summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.flayignore2
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile6
-rw-r--r--Gemfile.lock6
-rw-r--r--app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js17
-rw-r--r--app/assets/javascripts/pages/sessions/new/username_validator.js12
-rw-r--r--app/assets/javascripts/preview_markdown.js30
-rw-r--r--app/assets/stylesheets/pages/wiki.scss8
-rw-r--r--app/controllers/admin/broadcast_messages_controller.rb5
-rw-r--r--app/controllers/admin/services_controller.rb5
-rw-r--r--app/controllers/boards/issues_controller.rb6
-rw-r--r--app/controllers/concerns/uploads_actions.rb2
-rw-r--r--app/controllers/import/gitlab_projects_controller.rb5
-rw-r--r--app/controllers/projects/commits_controller.rb5
-rw-r--r--app/controllers/projects/cycle_analytics_controller.rb5
-rw-r--r--app/controllers/projects/forks_controller.rb5
-rw-r--r--app/controllers/projects/issues_controller.rb10
-rw-r--r--app/controllers/projects/merge_requests/creations_controller.rb5
-rw-r--r--app/controllers/projects/merge_requests_controller.rb6
-rw-r--r--app/controllers/projects/network_controller.rb5
-rw-r--r--app/controllers/projects/notes_controller.rb5
-rw-r--r--app/controllers/projects/pipelines_controller.rb6
-rw-r--r--app/controllers/projects/wikis_controller.rb4
-rw-r--r--app/controllers/projects_controller.rb5
-rw-r--r--app/controllers/registrations_controller.rb6
-rw-r--r--app/controllers/user_callouts_controller.rb23
-rw-r--r--app/helpers/user_callouts_helper.rb14
-rw-r--r--app/helpers/wiki_helper.rb18
-rw-r--r--app/models/clusters/applications/ingress.rb6
-rw-r--r--app/models/concerns/storage/legacy_namespace.rb16
-rw-r--r--app/models/group.rb6
-rw-r--r--app/models/namespace.rb18
-rw-r--r--app/models/project.rb8
-rw-r--r--app/models/project_wiki.rb4
-rw-r--r--app/models/upload.rb17
-rw-r--r--app/models/user.rb1
-rw-r--r--app/models/user_callout.rb13
-rw-r--r--app/models/wiki_page.rb66
-rw-r--r--app/services/system_note_service.rb26
-rw-r--r--app/uploaders/file_mover.rb4
-rw-r--r--app/uploaders/file_uploader.rb28
-rw-r--r--app/uploaders/gitlab_uploader.rb4
-rw-r--r--app/uploaders/records_uploads.rb13
-rw-r--r--app/views/admin/broadcast_messages/preview.js.haml1
-rw-r--r--app/views/projects/show.html.haml4
-rw-r--r--app/views/projects/wikis/_form.html.haml8
-rw-r--r--app/views/projects/wikis/edit.html.haml5
-rw-r--r--changelogs/unreleased/39985-enable-prometheus-metrics-for-deployed-ingresses.yml5
-rw-r--r--changelogs/unreleased/42270-fix-namespace-remove-exports-for-hashed-storage.yml6
-rw-r--r--changelogs/unreleased/42547-upload-store-mount-point.yml5
-rw-r--r--changelogs/unreleased/42684-set-up-ci-set-up-ci-cd.yml5
-rw-r--r--changelogs/unreleased/fj-37273-moving-wiki-pages-from-the-ui.yml5
-rw-r--r--changelogs/unreleased/osw-markdown-bypass-for-commit-messages.yml5
-rw-r--r--changelogs/unreleased/persistent-callouts.yml5
-rw-r--r--changelogs/unreleased/query-counts.yml5
-rw-r--r--config/initializers/gollum.rb82
-rw-r--r--config/initializers/query_limiting.rb9
-rw-r--r--config/routes.rb3
-rw-r--r--db/migrate/20180125214301_create_user_callouts.rb16
-rw-r--r--db/migrate/20180129193323_add_uploads_builder_context.rb14
-rw-r--r--db/schema.rb11
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/query_count_limits.md65
-rw-r--r--doc/topics/autodevops/index.md2
-rw-r--r--doc/user/project/pages/getting_started_part_two.md8
-rw-r--r--doc/user/project/repository/web_editor.md2
-rw-r--r--doc/user/project/wiki/img/wiki_move_page_1.pngbin0 -> 54550 bytes
-rw-r--r--doc/user/project/wiki/img/wiki_move_page_2.pngbin0 -> 33535 bytes
-rw-r--r--doc/user/project/wiki/index.md12
-rw-r--r--lib/api/api_guard.rb6
-rw-r--r--lib/api/branches.rb2
-rw-r--r--lib/api/issues.rb6
-rw-r--r--lib/api/merge_requests.rb6
-rw-r--r--lib/api/pipelines.rb2
-rw-r--r--lib/api/projects.rb2
-rw-r--r--lib/api/triggers.rb2
-rw-r--r--lib/api/users.rb2
-rw-r--r--lib/api/v3/branches.rb2
-rw-r--r--lib/api/v3/issues.rb6
-rw-r--r--lib/api/v3/merge_requests.rb4
-rw-r--r--lib/api/v3/pipelines.rb2
-rw-r--r--lib/api/v3/triggers.rb2
-rw-r--r--lib/gitlab/gfm/uploads_rewriter.rb2
-rw-r--r--lib/gitlab/git/repository.rb63
-rw-r--r--lib/gitlab/git/wiki.rb24
-rw-r--r--lib/gitlab/gitaly_client/operation_service.rb26
-rw-r--r--lib/gitlab/gitaly_client/wiki_page.rb5
-rw-r--r--lib/gitlab/kubernetes/helm/pod.rb8
-rw-r--r--lib/gitlab/query_limiting.rb36
-rw-r--r--lib/gitlab/query_limiting/active_support_subscriber.rb11
-rw-r--r--lib/gitlab/query_limiting/middleware.rb55
-rw-r--r--lib/gitlab/query_limiting/transaction.rb83
-rw-r--r--lib/tasks/flay.rake2
-rw-r--r--locale/gitlab.pot139
-rw-r--r--spec/controllers/user_callouts_controller_spec.rb49
-rw-r--r--spec/factories/projects.rb6
-rw-r--r--spec/factories/uploads.rb20
-rw-r--r--spec/factories/user_callouts.rb7
-rw-r--r--spec/features/projects/import_export/namespace_export_file_spec.rb55
-rw-r--r--spec/features/projects/wiki/user_updates_wiki_page_spec.rb74
-rw-r--r--spec/features/projects/wiki/user_views_wiki_page_spec.rb3
-rw-r--r--spec/helpers/user_callouts_helper_spec.rb47
-rw-r--r--spec/lib/file_size_validator_spec.rb2
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb2
-rw-r--r--spec/lib/gitlab/git/wiki_spec.rb36
-rw-r--r--spec/lib/gitlab/gitaly_client/operation_service_spec.rb49
-rw-r--r--spec/lib/gitlab/kubernetes/helm/pod_spec.rb4
-rw-r--r--spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb19
-rw-r--r--spec/lib/gitlab/query_limiting/middleware_spec.rb72
-rw-r--r--spec/lib/gitlab/query_limiting/transaction_spec.rb144
-rw-r--r--spec/lib/gitlab/query_limiting_spec.rb65
-rw-r--r--spec/models/namespace_spec.rb20
-rw-r--r--spec/models/project_spec.rb31
-rw-r--r--spec/models/project_wiki_spec.rb13
-rw-r--r--spec/models/upload_spec.rb6
-rw-r--r--spec/models/user_callout_spec.rb16
-rw-r--r--spec/models/wiki_page_spec.rb120
-rw-r--r--spec/requests/api/groups_spec.rb15
-rw-r--r--spec/services/system_note_service_spec.rb33
-rw-r--r--spec/uploaders/file_uploader_spec.rb29
-rw-r--r--spec/uploaders/gitlab_uploader_spec.rb2
-rw-r--r--vendor/ingress/values.yaml8
-rw-r--r--vendor/prometheus/values.yaml174
124 files changed, 2063 insertions, 300 deletions
diff --git a/.flayignore b/.flayignore
index acac0ce14c9..87cb3507b05 100644
--- a/.flayignore
+++ b/.flayignore
@@ -6,3 +6,5 @@ app/models/concerns/relative_positioning.rb
app/workers/stuck_merge_jobs_worker.rb
lib/gitlab/redis/*.rb
lib/gitlab/gitaly_client/operation_service.rb
+lib/gitlab/background_migration/*
+app/models/project_services/kubernetes_service.rb
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b4afa953175..9c3556f5cce 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -627,6 +627,8 @@ codequality:
sast:
<<: *except-docs
image: registry.gitlab.com/gitlab-org/gl-sast:latest
+ variables:
+ CONFIDENCE_LEVEL: 2
before_script: []
script:
- /app/bin/run .
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 4a7076db09a..bd14e8533ef 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.77.0
+0.78.0
diff --git a/Gemfile b/Gemfile
index 7bffae06b51..52ced4a132b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -69,6 +69,10 @@ gem 'net-ldap'
# Git Wiki
# 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
# Before updating this gem, check if
@@ -349,7 +353,7 @@ group :development, :test do
gem 'scss_lint', '~> 0.56.0', require: false
gem 'haml_lint', '~> 0.26.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 'benchmark-ips', '~> 2.3.0', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 2ddf8221a06..cf9f160499d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -211,7 +211,7 @@ GEM
fast_gettext (1.4.0)
ffaker (2.4.0)
ffi (1.9.18)
- flay (2.8.1)
+ flay (2.10.0)
erubis (~> 2.7.0)
path_expander (~> 1.0)
ruby_parser (~> 3.0)
@@ -588,7 +588,7 @@ GEM
ast (~> 2.3)
parslet (1.5.0)
blankslate (~> 2.0)
- path_expander (1.0.1)
+ path_expander (1.0.2)
peek (1.0.1)
concurrent-ruby (>= 0.9.0)
concurrent-ruby-ext (>= 0.9.0)
@@ -1037,7 +1037,7 @@ DEPENDENCIES
faraday (~> 0.12)
fast_blank
ffaker (~> 2.4)
- flay (~> 2.8.0)
+ flay (~> 2.10.0)
flipper (~> 0.11.0)
flipper-active_record (~> 0.11.0)
flipper-active_support_cache_store (~> 0.11.0)
diff --git a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
index 857a6793fe3..885acfac6d0 100644
--- a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
+++ b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
@@ -1,4 +1,7 @@
import _ from 'underscore';
+import axios from '~/lib/utils/axios_utils';
+import flash from '~/flash';
+import { __ } from '~/locale';
export default function initBroadcastMessagesForm() {
$('input#broadcast_message_color').on('input', function onMessageColorInput() {
@@ -18,13 +21,15 @@ export default function initBroadcastMessagesForm() {
if (message === '') {
$('.js-broadcast-message-preview').text('Your message here');
} else {
- $.ajax({
- url: previewPath,
- type: 'POST',
- data: {
- broadcast_message: { message },
+ axios.post(previewPath, {
+ broadcast_message: {
+ message,
},
- });
+ })
+ .then(({ data }) => {
+ $('.js-broadcast-message-preview').html(data.message);
+ })
+ .catch(() => flash(__('An error occurred while rendering preview broadcast message')));
}
}, 250));
}
diff --git a/app/assets/javascripts/pages/sessions/new/username_validator.js b/app/assets/javascripts/pages/sessions/new/username_validator.js
index bb34d5d2008..745543c22da 100644
--- a/app/assets/javascripts/pages/sessions/new/username_validator.js
+++ b/app/assets/javascripts/pages/sessions/new/username_validator.js
@@ -1,6 +1,9 @@
/* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */
import _ from 'underscore';
+import axios from '~/lib/utils/axios_utils';
+import flash from '~/flash';
+import { __ } from '~/locale';
const debounceTimeoutDuration = 1000;
const invalidInputClass = 'gl-field-error-outline';
@@ -77,12 +80,9 @@ export default class UsernameValidator {
this.state.pending = true;
this.state.available = false;
this.renderState();
- return $.ajax({
- type: 'GET',
- url: `${gon.relative_url_root}/users/${username}/exists`,
- dataType: 'json',
- success: (res) => this.setAvailabilityState(res.exists)
- });
+ axios.get(`${gon.relative_url_root}/users/${username}/exists`)
+ .then(({ data }) => this.setAvailabilityState(data.exists))
+ .catch(() => flash(__('An error occurred while validating username')));
}
}
diff --git a/app/assets/javascripts/preview_markdown.js b/app/assets/javascripts/preview_markdown.js
index 86c7b56198d..464bfb351e7 100644
--- a/app/assets/javascripts/preview_markdown.js
+++ b/app/assets/javascripts/preview_markdown.js
@@ -7,6 +7,10 @@
// more than `x` users are referenced.
//
+import axios from '~/lib/utils/axios_utils';
+import flash from '~/flash';
+import { __ } from '~/locale';
+
var lastTextareaPreviewed;
var lastTextareaHeight = null;
var markdownPreview;
@@ -62,21 +66,17 @@ MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) {
success(this.ajaxCache.response);
return;
}
- $.ajax({
- type: 'POST',
- url: url,
- data: {
- text: text
- },
- dataType: 'json',
- success: (function (response) {
- this.ajaxCache = {
- text: text,
- response: response
- };
- success(response);
- }).bind(this)
- });
+ axios.post(url, {
+ text,
+ })
+ .then(({ data }) => {
+ this.ajaxCache = {
+ text: text,
+ response: data,
+ };
+ success(data);
+ })
+ .catch(() => flash(__('An error occurred while fetching markdown preview')));
};
MarkdownPreview.prototype.hideReferencedUsers = function ($form) {
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index d8fec583121..e70a57c2a67 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -6,6 +6,14 @@
}
}
+.wiki-form {
+ .edit-wiki-page-slug-tip {
+ display: inline-block;
+ max-width: 100%;
+ margin-top: 5px;
+ }
+}
+
.title .edit-wiki-header {
width: 780px;
margin-left: auto;
diff --git a/app/controllers/admin/broadcast_messages_controller.rb b/app/controllers/admin/broadcast_messages_controller.rb
index c49b6459452..a9109a1d4d0 100644
--- a/app/controllers/admin/broadcast_messages_controller.rb
+++ b/app/controllers/admin/broadcast_messages_controller.rb
@@ -1,4 +1,6 @@
class Admin::BroadcastMessagesController < Admin::ApplicationController
+ include BroadcastMessagesHelper
+
before_action :finder, only: [:edit, :update, :destroy]
def index
@@ -37,7 +39,8 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
end
def preview
- @broadcast_message = BroadcastMessage.new(broadcast_message_params)
+ broadcast_message = BroadcastMessage.new(broadcast_message_params)
+ render json: { message: render_broadcast_message(broadcast_message) }
end
protected
diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb
index 4c3d336b3af..a7025b62ad7 100644
--- a/app/controllers/admin/services_controller.rb
+++ b/app/controllers/admin/services_controller.rb
@@ -1,6 +1,7 @@
class Admin::ServicesController < Admin::ApplicationController
include ServiceParams
+ before_action :whitelist_query_limiting, only: [:index]
before_action :service, only: [:edit, :update]
def index
@@ -37,4 +38,8 @@ class Admin::ServicesController < Admin::ApplicationController
def service
@service ||= Service.where(id: params[:id], template: true).first
end
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42430')
+ end
end
diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb
index f8049b20b9f..ee23ee0bcc3 100644
--- a/app/controllers/boards/issues_controller.rb
+++ b/app/controllers/boards/issues_controller.rb
@@ -2,6 +2,7 @@ module Boards
class IssuesController < Boards::ApplicationController
include BoardsResponses
+ before_action :whitelist_query_limiting, only: [:index, :update]
before_action :authorize_read_issue, only: [:index]
before_action :authorize_create_issue, only: [:create]
before_action :authorize_update_issue, only: [:update]
@@ -92,5 +93,10 @@ module Boards
}
)
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
diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb
index 61554029d09..7ad79a1e56c 100644
--- a/app/controllers/concerns/uploads_actions.rb
+++ b/app/controllers/concerns/uploads_actions.rb
@@ -70,7 +70,7 @@ module UploadsActions
end
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
end
diff --git a/app/controllers/import/gitlab_projects_controller.rb b/app/controllers/import/gitlab_projects_controller.rb
index 567957ba2cb..f22df992fe9 100644
--- a/app/controllers/import/gitlab_projects_controller.rb
+++ b/app/controllers/import/gitlab_projects_controller.rb
@@ -1,4 +1,5 @@
class Import::GitlabProjectsController < Import::BaseController
+ before_action :whitelist_query_limiting, only: [:create]
before_action :verify_gitlab_project_import_enabled
def new
@@ -40,4 +41,8 @@ class Import::GitlabProjectsController < Import::BaseController
:path, :namespace_id, :file
)
end
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42437')
+ end
end
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index 0a40c67368f..1d910e461b1 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -4,6 +4,7 @@ class Projects::CommitsController < Projects::ApplicationController
include ExtractsPath
include RendersCommits
+ before_action :whitelist_query_limiting
before_action :require_non_empty_project
before_action :assign_ref_vars
before_action :authorize_download_code!
@@ -65,4 +66,8 @@ class Projects::CommitsController < Projects::ApplicationController
@commits = @commits.with_pipeline_status
@commits = prepare_commits_for_rendering(@commits)
end
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42330')
+ end
end
diff --git a/app/controllers/projects/cycle_analytics_controller.rb b/app/controllers/projects/cycle_analytics_controller.rb
index 88ac3ad046b..d1b8fd80c4e 100644
--- a/app/controllers/projects/cycle_analytics_controller.rb
+++ b/app/controllers/projects/cycle_analytics_controller.rb
@@ -3,6 +3,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
include ActionView::Helpers::TextHelper
include CycleAnalyticsParams
+ before_action :whitelist_query_limiting, only: [:show]
before_action :authorize_read_cycle_analytics!
def show
@@ -31,4 +32,8 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
permissions: @cycle_analytics.permissions(user: current_user)
}
end
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42671')
+ end
end
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index 68978f8fdd1..f43bba18d81 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -2,6 +2,7 @@ class Projects::ForksController < Projects::ApplicationController
include ContinueParams
# Authorize
+ before_action :whitelist_query_limiting, only: [:create]
before_action :require_non_empty_project
before_action :authorize_download_code!
before_action :authenticate_user!, only: [:new, :create]
@@ -54,4 +55,8 @@ class Projects::ForksController < Projects::ApplicationController
render :error
end
end
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42335')
+ end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 384f18b316c..515cb08f1fc 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -8,6 +8,7 @@ class Projects::IssuesController < Projects::ApplicationController
prepend_before_action :authenticate_user!, only: [:new]
+ before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update]
before_action :check_issues_available!
before_action :issue, except: [:index, :new, :create, :bulk_update]
before_action :set_issuables_index, only: [:index]
@@ -247,4 +248,13 @@ class Projects::IssuesController < Projects::ApplicationController
@finder_type = IssuesFinder
super
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
end
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
index 0df80fa700f..a5a2d54ba82 100644
--- a/app/controllers/projects/merge_requests/creations_controller.rb
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -4,6 +4,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
include RendersCommits
skip_before_action :merge_request
+ before_action :whitelist_query_limiting, only: [:create]
before_action :authorize_create_merge_request!
before_action :apply_diff_view_cookie!, only: [:diffs, :diff_for_path]
before_action :build_merge_request, except: [:create]
@@ -125,4 +126,8 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
@project.forked_from_project
end
end
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42384')
+ end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 2e8a738b6d9..8af4e379f0a 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -7,6 +7,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
include IssuableCollections
skip_before_action :merge_request, only: [:index, :bulk_update]
+ before_action :whitelist_query_limiting, only: [:assign_related_issues, :update]
before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort]
before_action :set_issuables_index, only: [:index]
before_action :authenticate_user!, only: [:assign_related_issues]
@@ -339,4 +340,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
access_denied! unless access_check
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
end
diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb
index fb68dd771a1..3b10a93e97f 100644
--- a/app/controllers/projects/network_controller.rb
+++ b/app/controllers/projects/network_controller.rb
@@ -2,6 +2,7 @@ class Projects::NetworkController < Projects::ApplicationController
include ExtractsPath
include ApplicationHelper
+ before_action :whitelist_query_limiting
before_action :require_non_empty_project
before_action :assign_ref_vars
before_action :authorize_download_code!
@@ -35,4 +36,8 @@ class Projects::NetworkController < Projects::ApplicationController
@options[:extended_sha1] = params[:extended_sha1]
@commit = @repo.commit(@options[:extended_sha1])
end
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42333')
+ end
end
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 5940fae8dd0..4f8978c93c3 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -2,6 +2,7 @@ class Projects::NotesController < Projects::ApplicationController
include NotesActions
include ToggleAwardEmoji
+ before_action :whitelist_query_limiting, only: [:create]
before_action :authorize_read_note!
before_action :authorize_create_note!, only: [:create]
before_action :authorize_resolve_note!, only: [:resolve, :unresolve]
@@ -79,4 +80,8 @@ class Projects::NotesController < Projects::ApplicationController
access_denied! unless can?(current_user, :create_note, noteable)
end
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42383')
+ end
end
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index e146d0d3cd5..78d109cf33e 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -1,4 +1,5 @@
class Projects::PipelinesController < Projects::ApplicationController
+ before_action :whitelist_query_limiting, only: [:create, :retry]
before_action :pipeline, except: [:index, :new, :create, :charts]
before_action :commit, only: [:show, :builds, :failures]
before_action :authorize_read_pipeline!
@@ -166,4 +167,9 @@ class Projects::PipelinesController < Projects::ApplicationController
def commit
@commit ||= @pipeline.commit
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
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 292e4158f8b..fde96cbd35d 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -54,8 +54,8 @@ class Projects::WikisController < Projects::ApplicationController
else
render 'edit'
end
- rescue WikiPage::PageChangedError
- @conflict = true
+ rescue WikiPage::PageChangedError, WikiPage::PageRenameError => e
+ @error = e
render 'edit'
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 86923909d07..72573e0765d 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -3,6 +3,7 @@ class ProjectsController < Projects::ApplicationController
include ExtractsPath
include PreviewMarkdown
+ before_action :whitelist_query_limiting, only: [:create]
before_action :authenticate_user!, except: [:index, :show, :activity, :refs]
before_action :redirect_git_extension, only: [:show]
before_action :project, except: [:index, :new, :create]
@@ -405,4 +406,8 @@ class ProjectsController < Projects::ApplicationController
#
redirect_to request.original_url.sub(%r{\.git/?\Z}, '') if params[:format] == 'git'
end
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42440')
+ end
end
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index d9142311b6f..1848c806c41 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -1,6 +1,8 @@
class RegistrationsController < Devise::RegistrationsController
include Recaptcha::Verify
+ before_action :whitelist_query_limiting, only: [:destroy]
+
def new
redirect_to(new_user_session_path)
end
@@ -83,4 +85,8 @@ class RegistrationsController < Devise::RegistrationsController
def devise_mapping
@devise_mapping ||= Devise.mappings[:user]
end
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42380')
+ end
end
diff --git a/app/controllers/user_callouts_controller.rb b/app/controllers/user_callouts_controller.rb
new file mode 100644
index 00000000000..18cde4a7b1a
--- /dev/null
+++ b/app/controllers/user_callouts_controller.rb
@@ -0,0 +1,23 @@
+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
diff --git a/app/helpers/user_callouts_helper.rb b/app/helpers/user_callouts_helper.rb
new file mode 100644
index 00000000000..6368e248c6e
--- /dev/null
+++ b/app/helpers/user_callouts_helper.rb
@@ -0,0 +1,14 @@
+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
diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb
index 815fab9e061..41f9eedd4bd 100644
--- a/app/helpers/wiki_helper.rb
+++ b/app/helpers/wiki_helper.rb
@@ -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
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
diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb
index 9024f1df1cd..aa5cf97756f 100644
--- a/app/models/clusters/applications/ingress.rb
+++ b/app/models/clusters/applications/ingress.rb
@@ -17,8 +17,12 @@ module Clusters
'stable/nginx-ingress'
end
+ def chart_values_file
+ "#{Rails.root}/vendor/#{name}/values.yaml"
+ end
+
def install_command
- Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart)
+ Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart, chart_values_file: chart_values_file)
end
end
end
diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb
index 99dbd4fbacf..b12c10a84de 100644
--- a/app/models/concerns/storage/legacy_namespace.rb
+++ b/app/models/concerns/storage/legacy_namespace.rb
@@ -87,20 +87,10 @@ module Storage
remove_exports!
end
- def remove_exports!
- Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete))
- end
-
- def export_path
- File.join(Gitlab::ImportExport.storage_path, full_path_was)
- end
+ def remove_legacy_exports!
+ legacy_export_path = File.join(Gitlab::ImportExport.storage_path, full_path_was)
- def full_path_was
- if parent
- parent.full_path + '/' + path_was
- else
- path_was
- end
+ FileUtils.rm_rf(legacy_export_path)
end
end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index 62b1322ebe6..5b7f1b38612 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -274,12 +274,6 @@ class Group < Namespace
list_of_ids.reverse.map { |group| variables[group.id] }.compact.flatten
end
- def full_path_was
- return path_was unless has_parent?
-
- "#{parent.full_path}/#{path_was}"
- end
-
def group_member(user)
if group_members.loaded?
group_members.find { |gm| gm.user_id == user.id }
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 5010dd73c11..7b82d076975 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -221,6 +221,24 @@ class Namespace < ActiveRecord::Base
has_parent?
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
def refresh_access_of_projects_invited_groups
diff --git a/app/models/project.rb b/app/models/project.rb
index 03c5475c31f..12d5f28f5ea 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -69,6 +69,7 @@ class Project < ActiveRecord::Base
before_destroy :remove_private_deploy_keys
after_destroy -> { run_after_commit { remove_pages } }
+ after_destroy :remove_exports
after_validation :check_pending_delete
@@ -1525,6 +1526,8 @@ class Project < ActiveRecord::Base
end
def export_path
+ return nil unless namespace.present? || hashed_storage?(:repository)
+
File.join(Gitlab::ImportExport.storage_path, disk_path)
end
@@ -1533,8 +1536,9 @@ class Project < ActiveRecord::Base
end
def remove_exports
- _, status = Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete))
- status.zero?
+ return nil unless export_path.present?
+
+ FileUtils.rm_rf(export_path)
end
def full_path_slug
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index 459d1673125..f6041da986c 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -119,6 +119,8 @@ class ProjectWiki
end
def delete_page(page, message = nil)
+ return unless page
+
wiki.delete_page(page.path, commit_details(:deleted, message, page.title))
update_project_activity
@@ -131,6 +133,8 @@ class ProjectWiki
end
def page_title_and_dir(title)
+ return unless title
+
title_array = title.split("/")
title = title_array.pop
[title, title_array.join("/")]
diff --git a/app/models/upload.rb b/app/models/upload.rb
index fb55fd8007b..2024228537a 100644
--- a/app/models/upload.rb
+++ b/app/models/upload.rb
@@ -30,7 +30,7 @@ class Upload < ActiveRecord::Base
end
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.retrieve_from_store!(identifier)
end
@@ -40,6 +40,13 @@ class Upload < ActiveRecord::Base
File.exist?(absolute_path)
end
+ def uploader_context
+ {
+ identifier: identifier,
+ secret: secret
+ }.compact
+ end
+
private
def checksummable?
@@ -62,11 +69,15 @@ class Upload < ActiveRecord::Base
!path.start_with?('/')
end
+ def uploader_class
+ Object.const_get(uploader)
+ end
+
def identifier
File.basename(path)
end
- def uploader_class
- Object.const_get(uploader)
+ def mount_point
+ super&.to_sym
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index cad118f5502..a57ba3740c9 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -135,6 +135,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 :custom_attributes, class_name: 'UserCustomAttribute'
+ has_many :callouts, class_name: 'UserCallout'
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
#
diff --git a/app/models/user_callout.rb b/app/models/user_callout.rb
new file mode 100644
index 00000000000..e4b69382626
--- /dev/null
+++ b/app/models/user_callout.rb
@@ -0,0 +1,13 @@
+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
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index e6254183baf..0f5536415f7 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -1,5 +1,6 @@
class WikiPage
PageChangedError = Class.new(StandardError)
+ PageRenameError = Class.new(StandardError)
include ActiveModel::Validations
include ActiveModel::Conversion
@@ -102,7 +103,7 @@ class WikiPage
# The hierarchy of the directory this page is contained in.
def directory
- wiki.page_title_and_dir(slug).last
+ wiki.page_title_and_dir(slug)&.last.to_s
end
# The processed/formatted content of this page.
@@ -177,7 +178,7 @@ class WikiPage
# Creates a new Wiki 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.
# :format - Optional symbol representing the
# content format. Can be any type
@@ -189,7 +190,7 @@ class WikiPage
# Returns the String SHA1 of the newly created page
# or False if the save was unsuccessful.
def create(attrs = {})
- @attributes.merge!(attrs)
+ update_attributes(attrs)
save(page_details: title) do
wiki.create_page(title, content, format, message)
@@ -204,24 +205,29 @@ class WikiPage
# See ProjectWiki::MARKUPS Hash for available formats.
# :message - Optional commit message to set on the new version.
# :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
# or False if the save was unsuccessful.
def update(attrs = {})
last_commit_sha = attrs.delete(: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
end
- attrs.slice!(:content, :format, :message, :title)
- @attributes.merge!(attrs)
- page_details =
- if title.present? && @page.title != title
- title
- else
- @page.url_path
+ update_attributes(attrs)
+
+ if title_changed?
+ page_details = title
+
+ if wiki.find_page(page_details).present?
+ @attributes[:title] = @page.url_path
+ raise PageRenameError
end
+ else
+ page_details = @page.url_path
+ end
save(page_details: page_details) do
wiki.update_page(
@@ -255,8 +261,44 @@ class WikiPage
page.version.to_s
end
+ def title_changed?
+ title.present? && self.class.unhyphenize(@page.url_path) != title
+ end
+
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
attributes[:slug] = @page.url_path
attributes[:title] = @page.title
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 06b23cd7076..2253d638e93 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -22,8 +22,7 @@ module SystemNoteService
commits_text = "#{total_count} commit".pluralize(total_count)
body = "added #{commits_text}\n\n"
- body << existing_commit_summary(noteable, existing_commits, oldrev)
- body << new_commit_summary(new_commits).join("\n")
+ body << commits_list(noteable, new_commits, existing_commits, oldrev)
body << "\n\n[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})"
create_note(NoteSummary.new(noteable, project, author, body, action: 'commit', commit_count: total_count))
@@ -481,7 +480,7 @@ module SystemNoteService
# Returns an Array of Strings
def new_commit_summary(new_commits)
new_commits.collect do |commit|
- "* #{commit.short_id} - #{escape_html(commit.title)}"
+ content_tag('li', "#{commit.short_id} - #{commit.title}")
end
end
@@ -604,6 +603,16 @@ module SystemNoteService
"#{cross_reference_note_prefix}#{gfm_reference}"
end
+ # Builds a list of existing and new commits according to existing_commits and
+ # new_commits methods.
+ # Returns a String wrapped in `ul` and `li` tags.
+ def commits_list(noteable, new_commits, existing_commits, oldrev)
+ existing_commit_summary = existing_commit_summary(noteable, existing_commits, oldrev)
+ new_commit_summary = new_commit_summary(new_commits).join
+
+ content_tag('ul', "#{existing_commit_summary}#{new_commit_summary}".html_safe)
+ end
+
# Build a single line summarizing existing commits being added in a merge
# request
#
@@ -640,11 +649,8 @@ module SystemNoteService
branch = noteable.target_branch
branch = "#{noteable.target_project_namespace}:#{branch}" if noteable.for_fork?
- "* #{commit_ids} - #{commits_text} from branch `#{branch}`\n"
- end
-
- def escape_html(text)
- Rack::Utils.escape_html(text)
+ branch_name = content_tag('code', branch)
+ content_tag('li', "#{commit_ids} - #{commits_text} from branch #{branch_name}".html_safe)
end
def url_helpers
@@ -661,4 +667,8 @@ module SystemNoteService
start_sha: oldrev
)
end
+
+ def content_tag(*args)
+ ActionController::Base.helpers.content_tag(*args)
+ end
end
diff --git a/app/uploaders/file_mover.rb b/app/uploaders/file_mover.rb
index e7af1483d23..8f56f09c9f7 100644
--- a/app/uploaders/file_mover.rb
+++ b/app/uploaders/file_mover.rb
@@ -49,11 +49,11 @@ class FileMover
end
def uploader
- @uploader ||= PersonalFileUploader.new(model, secret)
+ @uploader ||= PersonalFileUploader.new(model, secret: secret)
end
def temp_file_uploader
- @temp_file_uploader ||= PersonalFileUploader.new(nil, secret)
+ @temp_file_uploader ||= PersonalFileUploader.new(nil, secret: secret)
end
def revert
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index 85ae9863b13..2310e67cb2e 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -62,9 +62,11 @@ class FileUploader < GitlabUploader
attr_accessor :model
- def initialize(model, secret = nil)
+ def initialize(model, mounted_as = nil, **uploader_context)
+ super(model, nil, **uploader_context)
+
@model = model
- @secret = secret
+ apply_context!(uploader_context)
end
def base_dir
@@ -107,15 +109,17 @@ class FileUploader < GitlabUploader
self.file.filename
end
- # the upload does not hold the secret, but holds the path
- # which contains the secret: extract it
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)
@secret = matches[:secret]
@identifier = matches[:identifier]
end
-
- super
end
def secret
@@ -124,6 +128,18 @@ class FileUploader < GitlabUploader
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
(image_or_video? ? File.basename(filename, File.extname(filename)) : filename).gsub("]", "\\]")
end
diff --git a/app/uploaders/gitlab_uploader.rb b/app/uploaders/gitlab_uploader.rb
index b12829efe73..a9e5c028b03 100644
--- a/app/uploaders/gitlab_uploader.rb
+++ b/app/uploaders/gitlab_uploader.rb
@@ -29,6 +29,10 @@ class GitlabUploader < CarrierWave::Uploader::Base
delegate :base_dir, :file_storage?, to: :class
+ def initialize(model, mounted_as = nil, **uploader_context)
+ super(model, mounted_as)
+ end
+
def file_cache_storage?
cache_storage.is_a?(CarrierWave::Storage::File)
end
diff --git a/app/uploaders/records_uploads.rb b/app/uploaders/records_uploads.rb
index dfb8dccec57..458928bc067 100644
--- a/app/uploaders/records_uploads.rb
+++ b/app/uploaders/records_uploads.rb
@@ -24,7 +24,7 @@ module RecordsUploads
uploads.where(path: upload_path).delete_all
upload.destroy! if upload
- self.upload = build_upload_from_uploader(self)
+ self.upload = build_upload
upload.save!
end
end
@@ -39,12 +39,13 @@ module RecordsUploads
Upload.order(id: :desc).where(uploader: self.class.to_s)
end
- def build_upload_from_uploader(uploader)
+ def build_upload
Upload.new(
- size: uploader.file.size,
- path: uploader.upload_path,
- model: uploader.model,
- uploader: uploader.class.to_s
+ uploader: self.class.to_s,
+ size: file.size,
+ path: upload_path,
+ model: model,
+ mount_point: mounted_as
)
end
diff --git a/app/views/admin/broadcast_messages/preview.js.haml b/app/views/admin/broadcast_messages/preview.js.haml
deleted file mode 100644
index c72e59640d7..00000000000
--- a/app/views/admin/broadcast_messages/preview.js.haml
+++ /dev/null
@@ -1 +0,0 @@
-$('.js-broadcast-message-preview').html("#{j(render_broadcast_message(@broadcast_message))}");
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index d3e867e124c..888d820b04e 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -47,7 +47,7 @@
- if @repository.gitlab_ci_yml
%li
- = link_to _('CI configuration'), ci_configuration_path(@project)
+ = link_to _('CI/CD configuration'), ci_configuration_path(@project)
- if current_user && can_push_branch?(@project, @project.default_branch)
- unless @repository.changelog
@@ -65,7 +65,7 @@
- unless @repository.gitlab_ci_yml
%li.missing
= link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do
- #{ _('Set up CI') }
+ #{ _('Set up CI/CD') }
- if koding_enabled? && @repository.koding_yml.blank?
%li.missing
= link_to _('Set up Koding'), add_koding_stack_path(@project)
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 4e265bf733a..d285251d06f 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -9,7 +9,13 @@
.form-group
.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
.col-sm-12= f.label :format, class: 'control-label-full-width'
.col-sm-12
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index 0d77e5bd16d..9d3d4072027 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -1,10 +1,7 @@
- @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout
- page_title _("Edit"), @page.title.capitalize, _("Wiki")
-- if @conflict
- .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_errors(@error)
.wiki-page-header.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
diff --git a/changelogs/unreleased/39985-enable-prometheus-metrics-for-deployed-ingresses.yml b/changelogs/unreleased/39985-enable-prometheus-metrics-for-deployed-ingresses.yml
new file mode 100644
index 00000000000..5c45d0db602
--- /dev/null
+++ b/changelogs/unreleased/39985-enable-prometheus-metrics-for-deployed-ingresses.yml
@@ -0,0 +1,5 @@
+---
+title: Enable Prometheus metrics for deployed Ingresses
+merge_request: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/16866
+author: joshlambert
+type: changed
diff --git a/changelogs/unreleased/42270-fix-namespace-remove-exports-for-hashed-storage.yml b/changelogs/unreleased/42270-fix-namespace-remove-exports-for-hashed-storage.yml
new file mode 100644
index 00000000000..d7a8b6e6f81
--- /dev/null
+++ b/changelogs/unreleased/42270-fix-namespace-remove-exports-for-hashed-storage.yml
@@ -0,0 +1,6 @@
+---
+title: Fix export removal for hashed-storage projects within a renamed or deleted
+ namespace
+merge_request: 16658
+author:
+type: fixed
diff --git a/changelogs/unreleased/42547-upload-store-mount-point.yml b/changelogs/unreleased/42547-upload-store-mount-point.yml
new file mode 100644
index 00000000000..35ae022984e
--- /dev/null
+++ b/changelogs/unreleased/42547-upload-store-mount-point.yml
@@ -0,0 +1,5 @@
+---
+title: Added uploader metadata to the uploads.
+merge_request: 16779
+author:
+type: added
diff --git a/changelogs/unreleased/42684-set-up-ci-set-up-ci-cd.yml b/changelogs/unreleased/42684-set-up-ci-set-up-ci-cd.yml
new file mode 100644
index 00000000000..0ef28e2ee01
--- /dev/null
+++ b/changelogs/unreleased/42684-set-up-ci-set-up-ci-cd.yml
@@ -0,0 +1,5 @@
+---
+title: Rename button to enable CI/CD configuration to "Set up CI/CD"
+merge_request: 16870
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-37273-moving-wiki-pages-from-the-ui.yml b/changelogs/unreleased/fj-37273-moving-wiki-pages-from-the-ui.yml
new file mode 100644
index 00000000000..5b5310dcfef
--- /dev/null
+++ b/changelogs/unreleased/fj-37273-moving-wiki-pages-from-the-ui.yml
@@ -0,0 +1,5 @@
+---
+title: Allow moving wiki pages from the UI
+merge_request: 16313
+author:
+type: fixed
diff --git a/changelogs/unreleased/osw-markdown-bypass-for-commit-messages.yml b/changelogs/unreleased/osw-markdown-bypass-for-commit-messages.yml
new file mode 100644
index 00000000000..b2c1cd9710a
--- /dev/null
+++ b/changelogs/unreleased/osw-markdown-bypass-for-commit-messages.yml
@@ -0,0 +1,5 @@
+---
+title: Bypass commits title markdown on notes
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/persistent-callouts.yml b/changelogs/unreleased/persistent-callouts.yml
new file mode 100644
index 00000000000..ca949a3b96c
--- /dev/null
+++ b/changelogs/unreleased/persistent-callouts.yml
@@ -0,0 +1,5 @@
+---
+title: Add backend for persistently dismissably callouts
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/query-counts.yml b/changelogs/unreleased/query-counts.yml
new file mode 100644
index 00000000000..e01ff8a4ad8
--- /dev/null
+++ b/changelogs/unreleased/query-counts.yml
@@ -0,0 +1,5 @@
+---
+title: Track and act upon the number of executed queries
+merge_request:
+author:
+type: added
diff --git a/config/initializers/gollum.rb b/config/initializers/gollum.rb
index 0b86cac51a7..6dfaceb8427 100644
--- a/config/initializers/gollum.rb
+++ b/config/initializers/gollum.rb
@@ -35,6 +35,88 @@ module Gollum
[]
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
module Git
diff --git a/config/initializers/query_limiting.rb b/config/initializers/query_limiting.rb
new file mode 100644
index 00000000000..66864d1898e
--- /dev/null
+++ b/config/initializers/query_limiting.rb
@@ -0,0 +1,9 @@
+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
diff --git a/config/routes.rb b/config/routes.rb
index f162043dd5e..e72ea1881cd 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -60,6 +60,9 @@ Rails.application.routes.draw do
resources :issues, module: :boards, only: [:index, :update]
end
+
+ # UserCallouts
+ resources :user_callouts, only: [:create]
end
# Koding route
diff --git a/db/migrate/20180125214301_create_user_callouts.rb b/db/migrate/20180125214301_create_user_callouts.rb
new file mode 100644
index 00000000000..856eff36ae0
--- /dev/null
+++ b/db/migrate/20180125214301_create_user_callouts.rb
@@ -0,0 +1,16 @@
+# 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
diff --git a/db/migrate/20180129193323_add_uploads_builder_context.rb b/db/migrate/20180129193323_add_uploads_builder_context.rb
new file mode 100644
index 00000000000..b3909a770ca
--- /dev/null
+++ b/db/migrate/20180129193323_add_uploads_builder_context.rb
@@ -0,0 +1,14 @@
+# 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
diff --git a/db/schema.rb b/db/schema.rb
index c701a5f1e17..50a2ceaaeee 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -1751,6 +1751,8 @@ ActiveRecord::Schema.define(version: 20180202111106) do
t.string "model_type"
t.string "uploader", null: false
t.datetime "created_at", null: false
+ t.string "mount_point"
+ t.string "secret"
end
add_index "uploads", ["checksum"], name: "index_uploads_on_checksum", using: :btree
@@ -1769,6 +1771,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
+ 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|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
@@ -2040,6 +2050,7 @@ ActiveRecord::Schema.define(version: 20180202111106) do
add_foreign_key "todos", "projects", name: "fk_45054f9c45", on_delete: :cascade
add_foreign_key "trending_projects", "projects", on_delete: :cascade
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_synced_attributes_metadata", "users", on_delete: :cascade
add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade
diff --git a/doc/development/README.md b/doc/development/README.md
index 12cca9f84b7..45e9565f9a7 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -75,6 +75,7 @@ comments: false
- [Ordering table columns](ordering_table_columns.md)
- [Verifying database capabilities](verifying_database_capabilities.md)
- [Database Debugging and Troubleshooting](database_debugging.md)
+- [Query Count Limits](query_count_limits.md)
## Testing guides
diff --git a/doc/development/query_count_limits.md b/doc/development/query_count_limits.md
new file mode 100644
index 00000000000..ebb6e0c2dac
--- /dev/null
+++ b/doc/development/query_count_limits.md
@@ -0,0 +1,65 @@
+# 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
+```
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 144cd4c26b0..0fab752afad 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -395,7 +395,7 @@ If you want to modify the CI/CD pipeline used by Auto DevOps, you can copy the
Assuming that your project is new or it doesn't have a `.gitlab-ci.yml` file
present:
-1. From your project home page, either click on the "Set up CI" button, or click
+1. From your project home page, either click on the "Set up CI/CD" button, or click
on the plus button and (`+`), then "New file"
1. Pick `.gitlab-ci.yml` as the template type
1. Select "Auto-DevOps" from the template dropdown
diff --git a/doc/user/project/pages/getting_started_part_two.md b/doc/user/project/pages/getting_started_part_two.md
index 64de0463dad..4a724dd5c1b 100644
--- a/doc/user/project/pages/getting_started_part_two.md
+++ b/doc/user/project/pages/getting_started_part_two.md
@@ -77,7 +77,7 @@ is useful for submitting merge requests to the upstream.
>
> 2. Why do I need to enable Shared Runners?
>
-> Shared Runners will run the script set by your GitLab CI
+> Shared Runners will run the script set by your GitLab CI/CD
configuration file. They're enabled by default to new projects,
but not to forks.
@@ -88,9 +88,9 @@ click **New project**, and name it considering the
[practical examples](getting_started_part_one.md#practical-examples).
1. Clone it to your local computer, add your website
files to your project, add, commit and push to GitLab.
-1. From the your **Project**'s page, click **Set up CI**:
+1. From the your **Project**'s page, click **Set up CI/CD**:
- ![setup GitLab CI](img/setup_ci.png)
+ ![setup GitLab CI/CD](img/setup_ci.png)
1. Choose one of the templates from the dropbox menu.
Pick up the template corresponding to the SSG you're using (or plain HTML).
@@ -98,7 +98,7 @@ Pick up the template corresponding to the SSG you're using (or plain HTML).
![gitlab-ci templates](img/choose_ci_template.png)
Once you have both site files and `.gitlab-ci.yml` in your project's
-root, GitLab CI will build your site and deploy it with Pages.
+root, GitLab CI/CD will build your site and deploy it with Pages.
Once the first build passes, you see your site is live by
navigating to your **Project**'s **Settings** > **Pages**,
where you'll find its default URL.
diff --git a/doc/user/project/repository/web_editor.md b/doc/user/project/repository/web_editor.md
index db0c3ed9d59..33c9a1a4d6b 100644
--- a/doc/user/project/repository/web_editor.md
+++ b/doc/user/project/repository/web_editor.md
@@ -45,7 +45,7 @@ has already been created, which creates a link to the license itself.
![New file button](img/web_editor_template_dropdown_buttons.png)
>**Note:**
-The **Set up CI** button will not appear on an empty repository. You have to at
+The **Set up CI/CD** button will not appear on an empty repository. You have to at
least add a file in order for the button to show up.
## Upload a file
diff --git a/doc/user/project/wiki/img/wiki_move_page_1.png b/doc/user/project/wiki/img/wiki_move_page_1.png
new file mode 100644
index 00000000000..0331c9d3a5c
--- /dev/null
+++ b/doc/user/project/wiki/img/wiki_move_page_1.png
Binary files differ
diff --git a/doc/user/project/wiki/img/wiki_move_page_2.png b/doc/user/project/wiki/img/wiki_move_page_2.png
new file mode 100644
index 00000000000..a8e0c055051
--- /dev/null
+++ b/doc/user/project/wiki/img/wiki_move_page_2.png
Binary files differ
diff --git a/doc/user/project/wiki/index.md b/doc/user/project/wiki/index.md
index c0b8a87f038..d084ee41d8a 100644
--- a/doc/user/project/wiki/index.md
+++ b/doc/user/project/wiki/index.md
@@ -64,6 +64,18 @@ effect.
You can find the **Delete** button only when editing a page. Click on it and
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
Every wiki has a sidebar from which a short list of the created pages can be
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index 9aeebc34525..c2113551207 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -42,7 +42,7 @@ module API
include Gitlab::Auth::UserAuthFinders
def find_current_user!
- user = find_user_from_access_token || find_user_from_warden
+ user = find_user_from_sources
return unless user
forbidden!('User is blocked') unless Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api)
@@ -50,6 +50,10 @@ module API
user
end
+ def find_user_from_sources
+ find_user_from_access_token || find_user_from_warden
+ end
+
private
# An array of scopes that were registered (using `allow_access_with_scope`)
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 0791a110c39..1794207e29b 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -29,6 +29,8 @@ module API
use :pagination
end
get ':id/repository/branches' do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42329')
+
repository = user_project.repository
branches = ::Kaminari.paginate_array(repository.branches.sort_by(&:name))
merged_branch_names = repository.merged_branch_names(branches.map(&:name))
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index c99fe3ab5b3..b6c278c89d0 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -161,6 +161,8 @@ module API
use :issue_params
end
post ':id/issues' do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42320')
+
authorize! :create_issue, user_project
# Setting created_at time only allowed for admins and project owners
@@ -201,6 +203,8 @@ module API
:labels, :created_at, :due_date, :confidential, :state_event
end
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))
authorize! :update_issue, issue
@@ -234,6 +238,8 @@ module API
requires :to_project_id, type: Integer, desc: 'The ID of the new project'
end
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])
not_found!('Issue') unless issue
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 420aaf1c964..719afa09295 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -152,6 +152,8 @@ module API
use :optional_params
end
post ":id/merge_requests" do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42316')
+
authorize! :create_merge_request, user_project
mr_params = declared_params(include_missing: false)
@@ -256,6 +258,8 @@ module API
at_least_one_of(*at_least_one_of_ce)
end
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)
mr_params = declared_params(include_missing: false)
@@ -283,6 +287,8 @@ module API
optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch'
end
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_when_pipeline_succeeds = to_boolean(params[:merge_when_pipeline_succeeds])
diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb
index 675c963bae2..d2b8b832e4e 100644
--- a/lib/api/pipelines.rb
+++ b/lib/api/pipelines.rb
@@ -42,6 +42,8 @@ module API
requires :ref, type: String, desc: 'Reference'
end
post ':id/pipeline' do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42124')
+
authorize! :create_pipeline, user_project
new_pipeline = Ci::CreatePipelineService.new(user_project,
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 8b5e4f8edcc..5b481121a10 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -210,6 +210,8 @@ module API
optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into'
end
post ':id/fork' do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42284')
+
fork_params = declared_params(include_missing: false)
namespace_id = fork_params[:namespace]
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index dd6801664b1..b3709455bc3 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -15,6 +15,8 @@ module API
optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end
post ":id/(ref/:ref/)trigger/pipeline", requirements: { ref: /.+/ } do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42283')
+
# validate variables
params[:variables] = params[:variables].to_h
unless params[:variables].all? { |key, value| key.is_a?(String) && value.is_a?(String) }
diff --git a/lib/api/users.rb b/lib/api/users.rb
index e5de31ad51b..c7c2aa280d5 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -383,6 +383,8 @@ module API
optional :hard_delete, type: Boolean, desc: "Whether to remove a user's contributions"
end
delete ":id" do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42279')
+
authenticated_as_admin!
user = User.find_by(id: params[:id])
diff --git a/lib/api/v3/branches.rb b/lib/api/v3/branches.rb
index b201bf77667..25176c5b38e 100644
--- a/lib/api/v3/branches.rb
+++ b/lib/api/v3/branches.rb
@@ -14,6 +14,8 @@ module API
success ::API::Entities::Branch
end
get ":id/repository/branches" do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42276')
+
repository = user_project.repository
branches = repository.branches.sort_by(&:name)
merged_branch_names = repository.merged_branch_names(branches.map(&:name))
diff --git a/lib/api/v3/issues.rb b/lib/api/v3/issues.rb
index cb371fdbab8..b59947d81d9 100644
--- a/lib/api/v3/issues.rb
+++ b/lib/api/v3/issues.rb
@@ -134,6 +134,8 @@ module API
use :issue_params
end
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
unless current_user.admin? || user_project.owner == current_user
params.delete(:created_at)
@@ -169,6 +171,8 @@ module API
:labels, :created_at, :due_date, :confidential, :state_event
end
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))
authorize! :update_issue, issue
@@ -201,6 +205,8 @@ module API
requires :to_project_id, type: Integer, desc: 'The ID of the new project'
end
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])
not_found!('Issue') unless issue
diff --git a/lib/api/v3/merge_requests.rb b/lib/api/v3/merge_requests.rb
index 0a24fea52a3..ce216497996 100644
--- a/lib/api/v3/merge_requests.rb
+++ b/lib/api/v3/merge_requests.rb
@@ -91,6 +91,8 @@ module API
use :optional_params
end
post ":id/merge_requests" do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42126')
+
authorize! :create_merge_request, user_project
mr_params = declared_params(include_missing: false)
@@ -167,6 +169,8 @@ module API
:remove_source_branch
end
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)
mr_params = declared_params(include_missing: false)
diff --git a/lib/api/v3/pipelines.rb b/lib/api/v3/pipelines.rb
index c48cbd2b765..6d31c12f572 100644
--- a/lib/api/v3/pipelines.rb
+++ b/lib/api/v3/pipelines.rb
@@ -19,6 +19,8 @@ module API
desc: 'Either running, branches, or tags'
end
get ':id/pipelines' do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42123')
+
authorize! :read_pipeline, user_project
pipelines = PipelinesFinder.new(user_project, scope: params[:scope]).execute
diff --git a/lib/api/v3/triggers.rb b/lib/api/v3/triggers.rb
index 534911fde5c..34f07dfb486 100644
--- a/lib/api/v3/triggers.rb
+++ b/lib/api/v3/triggers.rb
@@ -16,6 +16,8 @@ module API
optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end
post ":id/(ref/:ref/)trigger/builds", requirements: { ref: /.+/ } do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42121')
+
# validate variables
params[:variables] = params[:variables].to_h
unless params[:variables].all? { |key, value| key.is_a?(String) && value.is_a?(String) }
diff --git a/lib/gitlab/gfm/uploads_rewriter.rb b/lib/gitlab/gfm/uploads_rewriter.rb
index 3fdc3c27f73..1b74f735679 100644
--- a/lib/gitlab/gfm/uploads_rewriter.rb
+++ b/lib/gitlab/gfm/uploads_rewriter.rb
@@ -46,7 +46,7 @@ module Gitlab
private
def find_file(project, secret, file)
- uploader = FileUploader.new(project, secret)
+ uploader = FileUploader.new(project, secret: secret)
uploader.retrieve_from_store!(file)
uploader.file
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 35eb4a097e9..ab1362a3bb0 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -1222,33 +1222,13 @@ module Gitlab
end
def 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)
+ gitaly_migrate(:squash) do |is_enabled|
+ if is_enabled
+ gitaly_operation_client.user_squash(user, squash_id, branch,
+ start_sha, end_sha, author, message)
+ else
+ git_squash(user, squash_id, branch, start_sha, end_sha, author, message)
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
@@ -2182,6 +2162,37 @@ module Gitlab
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:)
args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
run_git(args)
diff --git a/lib/gitlab/git/wiki.rb b/lib/gitlab/git/wiki.rb
index ccdb8975342..daa17fc72cf 100644
--- a/lib/gitlab/git/wiki.rb
+++ b/lib/gitlab/git/wiki.rb
@@ -25,8 +25,9 @@ module Gitlab
@repository.exists?
end
+ # Disabled because of https://gitlab.com/gitlab-org/gitaly/merge_requests/539
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
gitaly_write_page(name, format, content, commit_details)
gollum_wiki.clear_cache
@@ -47,8 +48,9 @@ module Gitlab
end
end
+ # Disable because of https://gitlab.com/gitlab-org/gitlab-ce/issues/42094
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
gitaly_update_page(page_path, title, format, content, commit_details)
gollum_wiki.clear_cache
@@ -68,8 +70,9 @@ module Gitlab
end
end
+ # Disable because of https://gitlab.com/gitlab-org/gitlab-ce/issues/42039
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
gitaly_find_page(title: title, version: version, dir: dir)
else
@@ -192,7 +195,10 @@ module Gitlab
assert_type!(format, Symbol)
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
rescue Gollum::DuplicatePageError => e
@@ -210,7 +216,15 @@ module Gitlab
assert_type!(format, Symbol)
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
end
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index cd2734b5a07..831cfd1e014 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -183,6 +183,32 @@ module Gitlab
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(
user, branch_name, commit_message, actions, author_email, author_name,
start_branch_name, start_repository)
diff --git a/lib/gitlab/gitaly_client/wiki_page.rb b/lib/gitlab/gitaly_client/wiki_page.rb
index 7339468e911..a02d15db5dd 100644
--- a/lib/gitlab/gitaly_client/wiki_page.rb
+++ b/lib/gitlab/gitaly_client/wiki_page.rb
@@ -4,6 +4,7 @@ module Gitlab
ATTRS = %i(title format url_path path name historical raw_data).freeze
include AttributesBag
+ include Gitlab::EncodingHelper
def initialize(params)
super
@@ -11,6 +12,10 @@ module Gitlab
# All gRPC strings in a response are frozen, so we get an unfrozen
# version here so appending to `raw_data` doesn't blow up.
@raw_data = @raw_data.dup
+
+ @title = encode_utf8(@title)
+ @path = encode_utf8(@path)
+ @name = encode_utf8(@name)
end
def historical?
diff --git a/lib/gitlab/kubernetes/helm/pod.rb b/lib/gitlab/kubernetes/helm/pod.rb
index a3216759cae..ca5e06009fa 100644
--- a/lib/gitlab/kubernetes/helm/pod.rb
+++ b/lib/gitlab/kubernetes/helm/pod.rb
@@ -64,7 +64,7 @@ module Gitlab
{
name: 'configuration-volume',
configMap: {
- name: 'values-content-configuration',
+ name: "values-content-configuration-#{command.name}",
items: [{ key: 'values', path: 'values.yaml' }]
}
}
@@ -81,7 +81,11 @@ module Gitlab
def create_config_map
resource = ::Kubeclient::Resource.new
- resource.metadata = { name: 'values-content-configuration', namespace: namespace_name, labels: { name: 'values-content-configuration' } }
+ resource.metadata = {
+ name: "values-content-configuration-#{command.name}",
+ namespace: namespace_name,
+ labels: { name: "values-content-configuration-#{command.name}" }
+ }
resource.data = { values: File.read(command.chart_values_file) }
kubeclient.create_config_map(resource)
end
diff --git a/lib/gitlab/query_limiting.rb b/lib/gitlab/query_limiting.rb
new file mode 100644
index 00000000000..f64f1757144
--- /dev/null
+++ b/lib/gitlab/query_limiting.rb
@@ -0,0 +1,36 @@
+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
diff --git a/lib/gitlab/query_limiting/active_support_subscriber.rb b/lib/gitlab/query_limiting/active_support_subscriber.rb
new file mode 100644
index 00000000000..66049c94ec6
--- /dev/null
+++ b/lib/gitlab/query_limiting/active_support_subscriber.rb
@@ -0,0 +1,11 @@
+module Gitlab
+ module QueryLimiting
+ class ActiveSupportSubscriber < ActiveSupport::Subscriber
+ attach_to :active_record
+
+ def sql(*)
+ Transaction.current&.increment
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/query_limiting/middleware.rb b/lib/gitlab/query_limiting/middleware.rb
new file mode 100644
index 00000000000..949ae79a047
--- /dev/null
+++ b/lib/gitlab/query_limiting/middleware.rb
@@ -0,0 +1,55 @@
+# 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
diff --git a/lib/gitlab/query_limiting/transaction.rb b/lib/gitlab/query_limiting/transaction.rb
new file mode 100644
index 00000000000..3cbafadb0d0
--- /dev/null
+++ b/lib/gitlab/query_limiting/transaction.rb
@@ -0,0 +1,83 @@
+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
diff --git a/lib/tasks/flay.rake b/lib/tasks/flay.rake
index b1e012e70c5..4b4881cecb8 100644
--- a/lib/tasks/flay.rake
+++ b/lib/tasks/flay.rake
@@ -2,7 +2,7 @@ desc 'Code duplication analyze via flay'
task :flay do
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
exit 1
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 94458d60e01..159521a750b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-01-30 14:59+0100\n"
-"PO-Revision-Date: 2018-01-30 14:59+0100\n"
+"POT-Creation-Date: 2018-02-05 16:03+0100\n"
+"PO-Revision-Date: 2018-02-05 16:03+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
@@ -23,6 +23,11 @@ msgid_plural "%d commits"
msgstr[0] ""
msgstr[1] ""
+msgid "%d commit behind"
+msgid_plural "%d commits behind"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d issue"
msgid_plural "%d issues"
msgstr[0] ""
@@ -157,6 +162,18 @@ msgstr ""
msgid "An error occurred while fetching sidebar data"
msgstr ""
+msgid "An error occurred while getting projects"
+msgstr ""
+
+msgid "An error occurred while loading filenames"
+msgstr ""
+
+msgid "An error occurred while rendering KaTeX"
+msgstr ""
+
+msgid "An error occurred while retrieving calendar activity"
+msgstr ""
+
msgid "An error occurred while retrieving diff"
msgstr ""
@@ -363,7 +380,7 @@ msgstr ""
msgid "CI / CD"
msgstr ""
-msgid "CI configuration"
+msgid "CI/CD configuration"
msgstr ""
msgid "CICD|Jobs"
@@ -480,6 +497,24 @@ msgstr ""
msgid "CiStatus|running"
msgstr ""
+msgid "CiVariables|Input variable key"
+msgstr ""
+
+msgid "CiVariables|Input variable value"
+msgstr ""
+
+msgid "CiVariables|Remove variable row"
+msgstr ""
+
+msgid "CiVariable|All environments"
+msgstr ""
+
+msgid "CiVariable|Protected"
+msgstr ""
+
+msgid "CiVariable|Toggle protected"
+msgstr ""
+
msgid "CircuitBreakerApiLink|circuitbreaker api"
msgstr ""
@@ -791,6 +826,9 @@ msgstr ""
msgid "Commits|An error occurred while fetching merge requests data."
msgstr ""
+msgid "Commits|Commit: %{commitText}"
+msgstr ""
+
msgid "Commits|History"
msgstr ""
@@ -890,6 +928,9 @@ msgstr ""
msgid "Copy URL to clipboard"
msgstr ""
+msgid "Copy branch name to clipboard"
+msgstr ""
+
msgid "Copy commit SHA to clipboard"
msgstr ""
@@ -1099,12 +1140,33 @@ msgstr ""
msgid "Environments|You don't have any environments right now."
msgstr ""
+msgid "Error fetching contributors data."
+msgstr ""
+
+msgid "Error fetching labels."
+msgstr ""
+
+msgid "Error fetching network graph."
+msgstr ""
+
msgid "Error fetching refs"
msgstr ""
+msgid "Error fetching usage ping data."
+msgstr ""
+
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error saving label update."
+msgstr ""
+
+msgid "Error updating status for all todos."
+msgstr ""
+
+msgid "Error updating todo status."
+msgstr ""
+
msgid "EventFilterBy|Filter by all"
msgstr ""
@@ -1716,12 +1778,6 @@ msgstr ""
msgid "PipelineSchedules|Inactive"
msgstr ""
-msgid "PipelineSchedules|Input variable key"
-msgstr ""
-
-msgid "PipelineSchedules|Input variable value"
-msgstr ""
-
msgid "PipelineSchedules|Next Run"
msgstr ""
@@ -1731,9 +1787,6 @@ msgstr ""
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr ""
-msgid "PipelineSchedules|Remove variable row"
-msgstr ""
-
msgid "PipelineSchedules|Take ownership"
msgstr ""
@@ -2087,7 +2140,7 @@ msgstr ""
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
-msgid "Set up CI"
+msgid "Set up CI/CD"
msgstr ""
msgid "Set up Koding"
@@ -2116,6 +2169,9 @@ msgstr[1] ""
msgid "Snippets"
msgstr ""
+msgid "Something went wrong on our end"
+msgstr ""
+
msgid "Something went wrong on our end."
msgstr ""
@@ -2424,6 +2480,18 @@ msgstr ""
msgid "There are problems accessing Git storage: "
msgstr ""
+msgid "There was an error saving your notification settings."
+msgstr ""
+
+msgid "There was an error when reseting email token."
+msgstr ""
+
+msgid "There was an error when subscribing to this label."
+msgstr ""
+
+msgid "There was an error when unsubscribing from this label."
+msgstr ""
+
msgid "This directory"
msgstr ""
@@ -2635,6 +2703,9 @@ msgstr ""
msgid "Trigger this manual action"
msgstr ""
+msgid "Type %{value} to confirm:"
+msgstr ""
+
msgid "Unable to reset project cache."
msgstr ""
@@ -2719,6 +2790,12 @@ msgstr ""
msgid "WikiClone|Start Gollum and edit locally"
msgstr ""
+msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title."
+msgstr ""
+
+msgid "WikiEdit|There is already a page with the same title in that path."
+msgstr ""
+
msgid "WikiEmptyPageError|You are not allowed to create wiki pages"
msgstr ""
@@ -2897,6 +2974,9 @@ msgstr[1] ""
msgid "mrWidget|Cancel automatic merge"
msgstr ""
+msgid "mrWidget|Check out branch"
+msgstr ""
+
msgid "mrWidget|Checking ability to merge automatically"
msgstr ""
@@ -2906,9 +2986,27 @@ msgstr ""
msgid "mrWidget|Cherry-pick this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Closed"
+msgstr ""
+
msgid "mrWidget|Closed by"
msgstr ""
+msgid "mrWidget|Closes"
+msgstr ""
+
+msgid "mrWidget|Did not close"
+msgstr ""
+
+msgid "mrWidget|Email patches"
+msgstr ""
+
+msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
+msgstr ""
+
+msgid "mrWidget|Mentions"
+msgstr ""
+
msgid "mrWidget|Merge"
msgstr ""
@@ -2921,6 +3019,9 @@ msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
+msgid "mrWidget|Plain diff"
+msgstr ""
+
msgid "mrWidget|Refresh"
msgstr ""
@@ -2936,6 +3037,9 @@ msgstr ""
msgid "mrWidget|Remove source branch"
msgstr ""
+msgid "mrWidget|Request to merge"
+msgstr ""
+
msgid "mrWidget|Resolve conflicts"
msgstr ""
@@ -2981,9 +3085,18 @@ msgstr ""
msgid "mrWidget|This project is archived, write access has been disabled"
msgstr ""
+msgid "mrWidget|You can merge this merge request manually using the"
+msgstr ""
+
msgid "mrWidget|You can remove source branch now"
msgstr ""
+msgid "mrWidget|command line"
+msgstr ""
+
+msgid "mrWidget|into"
+msgstr ""
+
msgid "mrWidget|to be merged automatically when the pipeline succeeds"
msgstr ""
diff --git a/spec/controllers/user_callouts_controller_spec.rb b/spec/controllers/user_callouts_controller_spec.rb
new file mode 100644
index 00000000000..48e2ff75cac
--- /dev/null
+++ b/spec/controllers/user_callouts_controller_spec.rb
@@ -0,0 +1,49 @@
+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
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 16d328a5bc2..20976977f21 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -93,6 +93,12 @@ FactoryBot.define do
avatar { fixture_file_upload('spec/fixtures/dk.png') }
end
+ trait :with_export do
+ after(:create) do |project, evaluator|
+ ProjectExportWorker.new.perform(project.creator.id, project.id)
+ end
+ end
+
trait :broken_storage do
after(:create) do |project|
project.update_column(:repository_storage, 'broken')
diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb
index c8cfe251d27..ff3a2a76acc 100644
--- a/spec/factories/uploads.rb
+++ b/spec/factories/uploads.rb
@@ -3,36 +3,40 @@ FactoryBot.define do
model { build(:project) }
size 100.kilobytes
uploader "AvatarUploader"
+ mount_point :avatar
+ secret nil
# we should build a mount agnostic upload by default
transient do
- mounted_as :avatar
- secret SecureRandom.hex
+ filename 'myfile.jpg'
end
# 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
- model { build(:personal_snippet) }
- path { File.join(secret, 'myfile.jpg') }
uploader "PersonalFileUploader"
+ path { File.join(secret, filename) }
+ model { build(:personal_snippet) }
+ secret SecureRandom.hex
end
trait :issuable_upload do
- path { File.join(secret, 'myfile.jpg') }
uploader "FileUploader"
+ path { File.join(secret, filename) }
+ secret SecureRandom.hex
end
trait :namespace_upload do
model { build(:group) }
- path { File.join(secret, 'myfile.jpg') }
+ path { File.join(secret, filename) }
uploader "NamespaceFileUploader"
+ secret SecureRandom.hex
end
trait :attachment_upload do
transient do
- mounted_as :attachment
+ mount_point :attachment
end
model { build(:note) }
diff --git a/spec/factories/user_callouts.rb b/spec/factories/user_callouts.rb
new file mode 100644
index 00000000000..528e442c14b
--- /dev/null
+++ b/spec/factories/user_callouts.rb
@@ -0,0 +1,7 @@
+FactoryBot.define do
+ factory :user_callout do
+ feature_name :gke_cluster_integration
+
+ user
+ end
+end
diff --git a/spec/features/projects/import_export/namespace_export_file_spec.rb b/spec/features/projects/import_export/namespace_export_file_spec.rb
index e76bc6f1220..c1fccf4a40b 100644
--- a/spec/features/projects/import_export/namespace_export_file_spec.rb
+++ b/spec/features/projects/import_export/namespace_export_file_spec.rb
@@ -1,44 +1,37 @@
require 'spec_helper'
feature 'Import/Export - Namespace export file cleanup', :js do
- let(:export_path) { "#{Dir.tmpdir}/import_file_spec" }
- let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys }
+ let(:export_path) { Dir.mktmpdir('namespace_export_file_spec') }
- let(:project) { create(:project) }
-
- background do
- allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
+ before do
+ allow(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
end
after do
FileUtils.rm_rf(export_path, secure: true)
end
- context 'admin user' do
+ shared_examples_for 'handling project exports on namespace change' do
+ let!(:old_export_path) { project.export_path }
+
before do
sign_in(create(:admin))
+
+ setup_export_project
end
context 'moving the namespace' do
- scenario 'removes the export file' do
- setup_export_project
-
- old_export_path = project.export_path.dup
-
+ it 'removes the export file' do
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)
end
end
context 'deleting the namespace' do
- scenario 'removes the export file' do
- setup_export_project
-
- old_export_path = project.export_path.dup
-
+ it 'removes the export file' do
expect(File).to exist(old_export_path)
project.namespace.destroy
@@ -46,17 +39,29 @@ feature 'Import/Export - Namespace export file cleanup', :js do
expect(File).not_to exist(old_export_path)
end
end
+ end
- def setup_export_project
- visit edit_project_path(project)
+ describe 'legacy storage' do
+ 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')
- end
+ expect(page).to have_content('Export project')
+
+ find(:link, 'Export project').send_keys(:return)
+
+ visit edit_project_path(project)
+
+ expect(page).to have_content('Download export')
end
end
diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
index 949d90a50ff..4d2a08afecc 100644
--- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
@@ -1,6 +1,7 @@
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) }
before do
@@ -143,6 +144,7 @@ describe 'User updates wiki page' do
expect(page).to have_field('wiki[message]', with: 'Update home')
fill_in(:wiki_content, with: 'My awesome wiki!')
+
click_button('Save changes')
expect(page).to have_content('Home')
@@ -151,4 +153,74 @@ describe 'User updates wiki page' do
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
diff --git a/spec/features/projects/wiki/user_views_wiki_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_page_spec.rb
index ff325aeadd3..e37436838fd 100644
--- a/spec/features/projects/wiki/user_views_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_page_spec.rb
@@ -1,6 +1,7 @@
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(:project) { create(:project, namespace: user.namespace) }
let(:wiki_page) do
diff --git a/spec/helpers/user_callouts_helper_spec.rb b/spec/helpers/user_callouts_helper_spec.rb
new file mode 100644
index 00000000000..27455705d23
--- /dev/null
+++ b/spec/helpers/user_callouts_helper_spec.rb
@@ -0,0 +1,47 @@
+require "spec_helper"
+
+describe UserCalloutsHelper do
+ let(:user) { create(:user) }
+
+ before do
+ allow(helper).to receive(:current_user).and_return(user)
+ end
+
+ describe '.show_gke_cluster_integration_callout?' do
+ let(:project) { create(:project) }
+
+ subject { helper.show_gke_cluster_integration_callout?(project) }
+
+ context 'when user can create a cluster' do
+ before do
+ allow(helper).to receive(:can?).with(anything, :create_cluster, anything)
+ .and_return(true)
+ end
+
+ context 'when user has not dismissed' do
+ before do
+ allow(helper).to receive(:user_dismissed?).and_return(false)
+ end
+
+ it { is_expected.to be true }
+ end
+
+ context 'when user dismissed' do
+ before do
+ allow(helper).to receive(:user_dismissed?).and_return(true)
+ end
+
+ it { is_expected.to be false }
+ end
+ end
+
+ context 'when user can not create a cluster' do
+ before do
+ allow(helper).to receive(:can?).with(anything, :create_cluster, anything)
+ .and_return(false)
+ end
+
+ it { is_expected.to be false }
+ end
+ end
+end
diff --git a/spec/lib/file_size_validator_spec.rb b/spec/lib/file_size_validator_spec.rb
index c44bc1840df..ebd907ecb7f 100644
--- a/spec/lib/file_size_validator_spec.rb
+++ b/spec/lib/file_size_validator_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
describe FileSizeValidator do
let(:validator) { described_class.new(options) }
- let(:attachment) { AttachmentUploader.new }
let(:note) { create(:note) }
+ let(:attachment) { AttachmentUploader.new(note) }
describe 'options uses an integer' do
let(:options) { { maximum: 10, attributes: { attachment: attachment } } }
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index df9f8a84aa5..ec1c7a96f92 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -2196,7 +2196,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
repository.squash(user, squash_id, opts)
end
- context 'sparse checkout' do
+ context 'sparse checkout', :skip_gitaly_mock do
let(:expected_files) { %w(files files/js files/js/application.js) }
before do
diff --git a/spec/lib/gitlab/git/wiki_spec.rb b/spec/lib/gitlab/git/wiki_spec.rb
new file mode 100644
index 00000000000..bd8dbf07fa7
--- /dev/null
+++ b/spec/lib/gitlab/git/wiki_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe Gitlab::Git::Wiki do
+ let(:project) { create(:project) }
+ let(:user) { project.owner }
+ let(:wiki) { ProjectWiki.new(project, user) }
+ let(:gollum_wiki) { wiki.wiki }
+
+ # Remove skip_gitaly_mock flag when gitaly_find_page when
+ # https://gitlab.com/gitlab-org/gitaly/merge_requests/539 gets merged
+ describe '#page', :skip_gitaly_mock do
+ it 'returns the right page' do
+ create_page('page1', 'content')
+ create_page('foo/page1', 'content')
+
+ expect(gollum_wiki.page(title: 'page1', dir: '').url_path).to eq 'page1'
+ expect(gollum_wiki.page(title: 'page1', dir: 'foo').url_path).to eq 'foo/page1'
+
+ destroy_page('page1')
+ destroy_page('page1', 'foo')
+ end
+ end
+
+ def create_page(name, content)
+ gollum_wiki.write_page(name, :markdown, content, commit_details)
+ end
+
+ def commit_details
+ Gitlab::Git::Wiki::CommitDetails.new(user.name, user.email, "test commit")
+ end
+
+ def destroy_page(title, dir = '')
+ page = gollum_wiki.page(title: title, dir: dir)
+ wiki.delete_page(page, "test commit")
+ end
+end
diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
index d9ec28ab02e..9fbdd73ee0e 100644
--- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
@@ -123,4 +123,53 @@ describe Gitlab::GitalyClient::OperationService do
expect(subject.branch_created).to be(false)
end
end
+
+ describe '#user_squash' do
+ let(:branch_name) { 'my-branch' }
+ let(:squash_id) { '1' }
+ let(:start_sha) { 'b83d6e391c22777fca1ed3012fce84f633d7fed0' }
+ let(:end_sha) { '54cec5282aa9f21856362fe321c800c236a61615' }
+ let(:commit_message) { 'Squash message' }
+ let(:request) do
+ Gitaly::UserSquashRequest.new(
+ repository: repository.gitaly_repository,
+ user: gitaly_user,
+ squash_id: squash_id.to_s,
+ branch: branch_name,
+ start_sha: start_sha,
+ end_sha: end_sha,
+ author: gitaly_user,
+ commit_message: commit_message
+ )
+ end
+ let(:squash_sha) { 'f00' }
+ let(:response) { Gitaly::UserSquashResponse.new(squash_sha: squash_sha) }
+
+ subject do
+ client.user_squash(user, squash_id, branch_name, start_sha, end_sha, user, commit_message)
+ end
+
+ it 'sends a user_squash message and returns the squash sha' do
+ expect_any_instance_of(Gitaly::OperationService::Stub)
+ .to receive(:user_squash).with(request, kind_of(Hash))
+ .and_return(response)
+
+ expect(subject).to eq(squash_sha)
+ end
+
+ context "when git_error is present" do
+ let(:response) do
+ Gitaly::UserSquashResponse.new(git_error: "something failed")
+ end
+
+ it "throws a PreReceive exception" do
+ expect_any_instance_of(Gitaly::OperationService::Stub)
+ .to receive(:user_squash).with(request, kind_of(Hash))
+ .and_return(response)
+
+ expect { subject }.to raise_error(
+ Gitlab::Git::Repository::GitError, "something failed")
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
index 0b8e97b8948..ebb6033f71e 100644
--- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
@@ -63,14 +63,14 @@ describe Gitlab::Kubernetes::Helm::Pod do
it 'should mount configMap specification in the volume' do
spec = subject.generate.spec
- expect(spec.volumes.first.configMap['name']).to eq('values-content-configuration')
+ expect(spec.volumes.first.configMap['name']).to eq("values-content-configuration-#{app.name}")
expect(spec.volumes.first.configMap['items'].first['key']).to eq('values')
expect(spec.volumes.first.configMap['items'].first['path']).to eq('values.yaml')
end
end
context 'without a configuration file' do
- let(:app) { create(:clusters_applications_ingress, cluster: cluster) }
+ let(:app) { create(:clusters_applications_helm, cluster: cluster) }
it_behaves_like 'helm pod'
diff --git a/spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb b/spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb
new file mode 100644
index 00000000000..b49bc5c328c
--- /dev/null
+++ b/spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe Gitlab::QueryLimiting::ActiveSupportSubscriber do
+ describe '#sql' do
+ it 'increments the number of executed SQL queries' do
+ transaction = double(:transaction)
+
+ allow(Gitlab::QueryLimiting::Transaction)
+ .to receive(:current)
+ .and_return(transaction)
+
+ expect(transaction)
+ .to receive(:increment)
+ .at_least(:once)
+
+ User.count
+ end
+ end
+end
diff --git a/spec/lib/gitlab/query_limiting/middleware_spec.rb b/spec/lib/gitlab/query_limiting/middleware_spec.rb
new file mode 100644
index 00000000000..a04bcdecb4b
--- /dev/null
+++ b/spec/lib/gitlab/query_limiting/middleware_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper'
+
+describe Gitlab::QueryLimiting::Middleware do
+ describe '#call' do
+ it 'runs the application with query limiting in place' do
+ middleware = described_class.new(-> (env) { env })
+
+ expect_any_instance_of(Gitlab::QueryLimiting::Transaction)
+ .to receive(:act_upon_results)
+
+ expect(middleware.call({ number: 10 }))
+ .to eq({ number: 10 })
+ end
+ end
+
+ describe '#action_name' do
+ let(:middleware) { described_class.new(-> (env) { env }) }
+
+ context 'using a Rails request' do
+ it 'returns the name of the controller and action' do
+ env = {
+ described_class::CONTROLLER_KEY => double(
+ :controller,
+ action_name: 'show',
+ class: double(:class, name: 'UsersController'),
+ content_type: 'text/html'
+ )
+ }
+
+ expect(middleware.action_name(env)).to eq('UsersController#show')
+ end
+
+ it 'includes the content type if this is not text/html' do
+ env = {
+ described_class::CONTROLLER_KEY => double(
+ :controller,
+ action_name: 'show',
+ class: double(:class, name: 'UsersController'),
+ content_type: 'application/json'
+ )
+ }
+
+ expect(middleware.action_name(env))
+ .to eq('UsersController#show (application/json)')
+ end
+ end
+
+ context 'using a Grape API request' do
+ it 'returns the name of the request method and endpoint path' do
+ env = {
+ described_class::ENDPOINT_KEY => double(
+ :endpoint,
+ route: double(:route, request_method: 'GET', path: '/foo')
+ )
+ }
+
+ expect(middleware.action_name(env)).to eq('GET /foo')
+ end
+
+ it 'returns nil if the route can not be retrieved' do
+ endpoint = double(:endpoint)
+ env = { described_class::ENDPOINT_KEY => endpoint }
+
+ allow(endpoint)
+ .to receive(:route)
+ .and_raise(RuntimeError)
+
+ expect(middleware.action_name(env)).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/query_limiting/transaction_spec.rb b/spec/lib/gitlab/query_limiting/transaction_spec.rb
new file mode 100644
index 00000000000..b4231fcd0fa
--- /dev/null
+++ b/spec/lib/gitlab/query_limiting/transaction_spec.rb
@@ -0,0 +1,144 @@
+require 'spec_helper'
+
+describe Gitlab::QueryLimiting::Transaction do
+ after do
+ Thread.current[described_class::THREAD_KEY] = nil
+ end
+
+ describe '.current' do
+ it 'returns nil when there is no transaction' do
+ expect(described_class.current).to be_nil
+ end
+
+ it 'returns the transaction when present' do
+ Thread.current[described_class::THREAD_KEY] = described_class.new
+
+ expect(described_class.current).to be_an_instance_of(described_class)
+ end
+ end
+
+ describe '.run' do
+ it 'runs a transaction and returns it and its return value' do
+ trans, ret = described_class.run do
+ 10
+ end
+
+ expect(trans).to be_an_instance_of(described_class)
+ expect(ret).to eq(10)
+ end
+
+ it 'removes the transaction from the current thread upon completion' do
+ described_class.run do
+ 10
+ end
+
+ expect(Thread.current[described_class::THREAD_KEY]).to be_nil
+ end
+ end
+
+ describe '#act_upon_results' do
+ context 'when the query threshold is not exceeded' do
+ it 'does nothing' do
+ trans = described_class.new
+
+ expect(trans).not_to receive(:raise)
+
+ trans.act_upon_results
+ end
+ end
+
+ context 'when the query threshold is exceeded' do
+ let(:transaction) do
+ trans = described_class.new
+ trans.count = described_class::THRESHOLD + 1
+
+ trans
+ end
+
+ it 'raises an error when this is enabled' do
+ expect { transaction.act_upon_results }
+ .to raise_error(described_class::ThresholdExceededError)
+ end
+
+ it 'reports the error in Sentry if raising an error is disabled' do
+ expect(transaction)
+ .to receive(:raise_error?)
+ .and_return(false)
+
+ expect(Raven)
+ .to receive(:capture_exception)
+ .with(an_instance_of(described_class::ThresholdExceededError))
+
+ transaction.act_upon_results
+ end
+ end
+ end
+
+ describe '#increment' do
+ it 'increments the number of executed queries' do
+ transaction = described_class.new
+
+ expect(transaction.count).to be_zero
+
+ transaction.increment
+
+ expect(transaction.count).to eq(1)
+ end
+ end
+
+ describe '#raise_error?' do
+ it 'returns true in a test environment' do
+ transaction = described_class.new
+
+ expect(transaction.raise_error?).to eq(true)
+ end
+
+ it 'returns false in a production environment' do
+ transaction = described_class.new
+
+ expect(Rails.env)
+ .to receive(:test?)
+ .and_return(false)
+
+ expect(transaction.raise_error?).to eq(false)
+ end
+ end
+
+ describe '#threshold_exceeded?' do
+ it 'returns false when the threshold is not exceeded' do
+ transaction = described_class.new
+
+ expect(transaction.threshold_exceeded?).to eq(false)
+ end
+
+ it 'returns true when the threshold is exceeded' do
+ transaction = described_class.new
+ transaction.count = described_class::THRESHOLD + 1
+
+ expect(transaction.threshold_exceeded?).to eq(true)
+ end
+ end
+
+ describe '#error_message' do
+ it 'returns the error message to display when the threshold is exceeded' do
+ transaction = described_class.new
+ transaction.count = max = described_class::THRESHOLD
+
+ expect(transaction.error_message).to eq(
+ "Too many SQL queries were executed: a maximum of #{max} " \
+ "is allowed but #{max} SQL queries were executed"
+ )
+ end
+
+ it 'includes the action name in the error message when present' do
+ transaction = described_class.new
+ transaction.count = max = described_class::THRESHOLD
+ transaction.action = 'UsersController#show'
+
+ expect(transaction.error_message).to eq(
+ "Too many SQL queries were executed in UsersController#show: " \
+ "a maximum of #{max} is allowed but #{max} SQL queries were executed"
+ )
+ end
+ end
+end
diff --git a/spec/lib/gitlab/query_limiting_spec.rb b/spec/lib/gitlab/query_limiting_spec.rb
new file mode 100644
index 00000000000..2eddab0b8c3
--- /dev/null
+++ b/spec/lib/gitlab/query_limiting_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+describe Gitlab::QueryLimiting do
+ describe '.enable?' do
+ it 'returns true in a test environment' do
+ expect(described_class.enable?).to eq(true)
+ end
+
+ it 'returns true in a development environment' do
+ allow(Rails.env).to receive(:development?).and_return(true)
+
+ expect(described_class.enable?).to eq(true)
+ end
+
+ it 'returns true on GitLab.com' do
+ allow(Gitlab).to receive(:com?).and_return(true)
+
+ expect(described_class.enable?).to eq(true)
+ end
+
+ it 'returns true in a non GitLab.com' do
+ expect(Gitlab).to receive(:com?).and_return(false)
+ expect(Rails.env).to receive(:development?).and_return(false)
+ expect(Rails.env).to receive(:test?).and_return(false)
+
+ expect(described_class.enable?).to eq(false)
+ end
+ end
+
+ describe '.whitelist' do
+ it 'raises ArgumentError when an invalid issue URL is given' do
+ expect { described_class.whitelist('foo') }
+ .to raise_error(ArgumentError)
+ end
+
+ context 'without a transaction' do
+ it 'does nothing' do
+ expect { described_class.whitelist('https://example.com') }
+ .not_to raise_error
+ end
+ end
+
+ context 'with a transaction' do
+ let(:transaction) { Gitlab::QueryLimiting::Transaction.new }
+
+ before do
+ allow(Gitlab::QueryLimiting::Transaction)
+ .to receive(:current)
+ .and_return(transaction)
+ end
+
+ it 'does not increment the number of SQL queries executed in the block' do
+ before = transaction.count
+
+ described_class.whitelist('https://example.com')
+
+ 2.times do
+ User.count
+ end
+
+ expect(transaction.count).to eq(before)
+ end
+ end
+ end
+end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 6b7dbad128c..824cca66fb4 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -596,4 +596,24 @@ describe Namespace do
end
end
end
+
+ describe '#remove_exports' do
+ let(:legacy_project) { create(:project, :with_export, namespace: namespace) }
+ let(:hashed_project) { create(:project, :with_export, :hashed, namespace: namespace) }
+ let(:export_path) { Dir.mktmpdir('namespace_remove_exports_spec') }
+ let(:legacy_export) { legacy_project.export_project_path }
+ let(:hashed_export) { hashed_project.export_project_path }
+
+ it 'removes exports for legacy and hashed projects' do
+ allow(Gitlab::ImportExport).to receive(:storage_path) { export_path }
+
+ expect(File.exist?(legacy_export)).to be_truthy
+ expect(File.exist?(hashed_export)).to be_truthy
+
+ namespace.remove_exports!
+
+ expect(File.exist?(legacy_export)).to be_falsy
+ expect(File.exist?(hashed_export)).to be_falsy
+ end
+ end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index df4c796c61e..da940571bc1 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -2503,6 +2503,37 @@ describe Project do
end
end
+ describe '#remove_exports' do
+ let(:project) { create(:project, :with_export) }
+
+ it 'removes the exports directory for the project' do
+ expect(File.exist?(project.export_path)).to be_truthy
+
+ allow(FileUtils).to receive(:rm_rf).and_call_original
+ expect(FileUtils).to receive(:rm_rf).with(project.export_path).and_call_original
+ project.remove_exports
+
+ expect(File.exist?(project.export_path)).to be_falsy
+ end
+
+ it 'is a no-op when there is no namespace' do
+ export_path = project.export_path
+ project.update_column(:namespace_id, nil)
+
+ expect(FileUtils).not_to receive(:rm_rf).with(export_path)
+
+ project.remove_exports
+
+ expect(File.exist?(export_path)).to be_truthy
+ end
+
+ it 'is run when the project is destroyed' do
+ expect(project).to receive(:remove_exports).and_call_original
+
+ project.destroy
+ end
+ end
+
describe '#forks_count' do
it 'returns the number of forks' do
project = build(:project)
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index 929086305ba..1e7671476f1 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -127,7 +127,7 @@ describe ProjectWiki do
end
after do
- destroy_page(subject.pages.first.page)
+ subject.pages.each { |page| destroy_page(page.page) }
end
it "returns the latest version of the page if it exists" do
@@ -148,6 +148,17 @@ describe ProjectWiki do
page = subject.find_page("index page")
expect(page).to be_a WikiPage
end
+
+ context 'pages with multibyte-character title' do
+ before do
+ create_page("autre pagé", "C'est un génial Gollum Wiki")
+ end
+
+ it "can find a page by slug" do
+ page = subject.find_page("autre pagé")
+ expect(page.title).to eq("autre pagé")
+ end
+ end
end
context 'when Gitaly wiki_find_page is enabled' do
diff --git a/spec/models/upload_spec.rb b/spec/models/upload_spec.rb
index 42f3d609770..0dcaa026332 100644
--- a/spec/models/upload_spec.rb
+++ b/spec/models/upload_spec.rb
@@ -103,4 +103,10 @@ describe Upload do
expect(upload).not_to exist
end
end
+
+ describe "#uploader_context" do
+ subject { create(:upload, :issuable_upload, secret: 'secret', filename: 'file.txt') }
+
+ it { expect(subject.uploader_context).to match(a_hash_including(secret: 'secret', identifier: 'file.txt')) }
+ end
end
diff --git a/spec/models/user_callout_spec.rb b/spec/models/user_callout_spec.rb
new file mode 100644
index 00000000000..64ba17c81fe
--- /dev/null
+++ b/spec/models/user_callout_spec.rb
@@ -0,0 +1,16 @@
+require 'rails_helper'
+
+describe UserCallout do
+ let!(:callout) { create(:user_callout) }
+
+ describe 'relationships' do
+ it { is_expected.to belong_to(:user) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:user) }
+
+ it { is_expected.to validate_presence_of(:feature_name) }
+ it { is_expected.to validate_uniqueness_of(:feature_name).scoped_to(:user_id).ignoring_case_sensitivity }
+ end
+end
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index 9840afe6c4e..4a1d12cd448 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -188,14 +188,37 @@ describe WikiPage do
end
end
- describe "#update" do
+ describe '#create', :skip_gitaly_mock do
+ context 'with valid attributes' do
+ it 'raises an error if a page with the same path already exists' do
+ create_page('New Page', 'content')
+ create_page('foo/bar', 'content')
+ expect { create_page('New Page', 'other content') }.to raise_error Gitlab::Git::Wiki::DuplicatePageError
+ expect { create_page('foo/bar', 'other content') }.to raise_error Gitlab::Git::Wiki::DuplicatePageError
+
+ destroy_page('New Page')
+ destroy_page('bar', 'foo')
+ end
+
+ it 'if the title is preceded by a / it is removed' do
+ create_page('/New Page', 'content')
+
+ expect(wiki.find_page('New Page')).not_to be_nil
+
+ destroy_page('New Page')
+ end
+ end
+ end
+
+ # Remove skip_gitaly_mock flag when gitaly_update_page implements moving pages
+ describe "#update", :skip_gitaly_mock do
before do
create_page("Update", "content")
@page = wiki.find_page("Update")
end
after do
- destroy_page(@page.title)
+ destroy_page(@page.title, @page.directory)
end
context "with valid attributes" do
@@ -233,6 +256,95 @@ describe WikiPage do
expect { @page.update(content: 'more content', last_commit_sha: 'xxx') }.to raise_error(WikiPage::PageChangedError)
end
end
+
+ context 'when renaming a page' do
+ it 'raises an error if the page already exists' do
+ create_page('Existing Page', 'content')
+
+ expect { @page.update(title: 'Existing Page', content: 'new_content') }.to raise_error(WikiPage::PageRenameError)
+ expect(@page.title).to eq 'Update'
+ expect(@page.content).to eq 'new_content'
+
+ destroy_page('Existing Page')
+ end
+
+ it 'updates the content and rename the file' do
+ new_title = 'Renamed Page'
+ new_content = 'updated content'
+
+ expect(@page.update(title: new_title, content: new_content)).to be_truthy
+
+ @page = wiki.find_page(new_title)
+
+ expect(@page).not_to be_nil
+ expect(@page.content).to eq new_content
+ end
+ end
+
+ context 'when moving a page' do
+ it 'raises an error if the page already exists' do
+ create_page('foo/Existing Page', 'content')
+
+ expect { @page.update(title: 'foo/Existing Page', content: 'new_content') }.to raise_error(WikiPage::PageRenameError)
+ expect(@page.title).to eq 'Update'
+ expect(@page.content).to eq 'new_content'
+
+ destroy_page('Existing Page', 'foo')
+ end
+
+ it 'updates the content and moves the file' do
+ new_title = 'foo/Other Page'
+ new_content = 'new_content'
+
+ expect(@page.update(title: new_title, content: new_content)).to be_truthy
+
+ page = wiki.find_page(new_title)
+
+ expect(page).not_to be_nil
+ expect(page.content).to eq new_content
+ end
+
+ context 'in subdir' do
+ before do
+ create_page('foo/Existing Page', 'content')
+ @page = wiki.find_page('foo/Existing Page')
+ end
+
+ it 'moves the page to the root folder if the title is preceded by /' do
+ expect(@page.slug).to eq 'foo/Existing-Page'
+ expect(@page.update(title: '/Existing Page', content: 'new_content')).to be_truthy
+ expect(@page.slug).to eq 'Existing-Page'
+ end
+
+ it 'does nothing if it has the same title' do
+ original_path = @page.slug
+
+ expect(@page.update(title: 'Existing Page', content: 'new_content')).to be_truthy
+ expect(@page.slug).to eq original_path
+ end
+ end
+
+ context 'in root dir' do
+ it 'does nothing if the title is preceded by /' do
+ original_path = @page.slug
+
+ expect(@page.update(title: '/Update', content: 'new_content')).to be_truthy
+ expect(@page.slug).to eq original_path
+ end
+ end
+ end
+
+ context "with invalid attributes" do
+ it 'aborts update if title blank' do
+ expect(@page.update(title: '', content: 'new_content')).to be_falsey
+ expect(@page.content).to eq 'new_content'
+
+ page = wiki.find_page('Update')
+ expect(page.content).to eq 'content'
+
+ @page.title = 'Update'
+ end
+ end
end
describe "#destroy" do
@@ -421,8 +533,8 @@ describe WikiPage do
wiki.wiki.write_page(name, :markdown, content, commit_details)
end
- def destroy_page(title)
- page = wiki.wiki.page(title: title)
+ def destroy_page(title, dir = '')
+ page = wiki.wiki.page(title: title, dir: dir)
wiki.delete_page(page, "test commit")
end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 3c0b4728dc2..bb0034e3237 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -30,6 +30,21 @@ describe API::Groups do
expect(json_response)
.to satisfy_one { |group| group['name'] == group1.name }
end
+
+ it 'avoids N+1 queries' do
+ # Establish baseline
+ get api("/groups", admin)
+
+ control = ActiveRecord::QueryRecorder.new do
+ get api("/groups", admin)
+ end
+
+ create(:group)
+
+ expect do
+ get api("/groups", admin)
+ end.not_to exceed_query_limit(control)
+ end
end
context "when authenticated as user" do
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index ab3aa18cf4e..5b5edc1aa0d 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -54,10 +54,11 @@ describe SystemNoteService do
expect(note_lines[0]).to eq "added #{new_commits.size} commits"
end
- it 'adds a message line for each commit' do
- new_commits.each_with_index do |commit, i|
- # Skip the header
- expect(HTMLEntities.new.decode(note_lines[i + 1])).to eq "* #{commit.short_id} - #{commit.title}"
+ it 'adds a message for each commit' do
+ decoded_note_content = HTMLEntities.new.decode(subject.note)
+
+ new_commits.each do |commit|
+ expect(decoded_note_content).to include("<li>#{commit.short_id} - #{commit.title}</li>")
end
end
end
@@ -69,7 +70,7 @@ describe SystemNoteService do
let(:old_commits) { [noteable.commits.last] }
it 'includes the existing commit' do
- expect(summary_line).to eq "* #{old_commits.first.short_id} - 1 commit from branch `feature`"
+ expect(summary_line).to start_with("<ul><li>#{old_commits.first.short_id} - 1 commit from branch <code>feature</code>")
end
end
@@ -79,22 +80,16 @@ describe SystemNoteService do
context 'with oldrev' do
let(:oldrev) { noteable.commits[2].id }
- it 'includes a commit range' do
- expect(summary_line).to start_with "* #{Commit.truncate_sha(oldrev)}...#{old_commits.last.short_id}"
- end
-
- it 'includes a commit count' do
- expect(summary_line).to end_with " - 26 commits from branch `feature`"
+ it 'includes a commit range and count' do
+ expect(summary_line)
+ .to start_with("<ul><li>#{Commit.truncate_sha(oldrev)}...#{old_commits.last.short_id} - 26 commits from branch <code>feature</code>")
end
end
context 'without oldrev' do
- it 'includes a commit range' do
- expect(summary_line).to start_with "* #{old_commits[0].short_id}..#{old_commits[-1].short_id}"
- end
-
- it 'includes a commit count' do
- expect(summary_line).to end_with " - 26 commits from branch `feature`"
+ it 'includes a commit range and count' do
+ expect(summary_line)
+ .to start_with("<ul><li>#{old_commits[0].short_id}..#{old_commits[-1].short_id} - 26 commits from branch <code>feature</code>")
end
end
@@ -104,7 +99,7 @@ describe SystemNoteService do
end
it 'includes the project namespace' do
- expect(summary_line).to end_with "`#{noteable.target_project_namespace}:feature`"
+ expect(summary_line).to include("<code>#{noteable.target_project_namespace}:feature</code>")
end
end
end
@@ -693,7 +688,7 @@ describe SystemNoteService do
describe '.new_commit_summary' do
it 'escapes HTML titles' do
commit = double(title: '<pre>This is a test</pre>', short_id: '12345678')
- escaped = '&lt;pre&gt;This is a test&lt;&#x2F;pre&gt;'
+ escaped = '&lt;pre&gt;This is a test&lt;/pre&gt;'
expect(described_class.new_commit_summary([commit])).to all(match(/- #{escaped}/))
end
diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb
index a72f853df75..a559681a079 100644
--- a/spec/uploaders/file_uploader_spec.rb
+++ b/spec/uploaders/file_uploader_spec.rb
@@ -40,7 +40,7 @@ describe FileUploader do
end
describe 'initialize' do
- let(:uploader) { described_class.new(double, 'secret') }
+ let(:uploader) { described_class.new(double, secret: 'secret') }
it 'accepts a secret parameter' do
expect(described_class).not_to receive(:generate_secret)
@@ -54,4 +54,31 @@ describe FileUploader do
expect(uploader.secret).to eq('secret')
end
end
+
+ describe '#upload=' do
+ let(:secret) { SecureRandom.hex }
+ let(:upload) { create(:upload, :issuable_upload, secret: secret, filename: 'file.txt') }
+
+ it 'handles nil' do
+ expect(uploader).not_to receive(:apply_context!)
+
+ uploader.upload = nil
+ end
+
+ it 'extract the uploader context from it' do
+ expect(uploader).to receive(:apply_context!).with(a_hash_including(secret: secret, identifier: 'file.txt'))
+
+ uploader.upload = upload
+ end
+
+ context 'uploader_context is empty' do
+ it 'fallbacks to regex based extraction' do
+ expect(upload).to receive(:uploader_context).and_return({})
+
+ uploader.upload = upload
+ expect(uploader.secret).to eq(secret)
+ expect(uploader.instance_variable_get(:@identifier)).to eq('file.txt')
+ end
+ end
+ end
end
diff --git a/spec/uploaders/gitlab_uploader_spec.rb b/spec/uploaders/gitlab_uploader_spec.rb
index a144b39f74f..60e35dcf235 100644
--- a/spec/uploaders/gitlab_uploader_spec.rb
+++ b/spec/uploaders/gitlab_uploader_spec.rb
@@ -4,7 +4,7 @@ require 'carrierwave/storage/fog'
describe GitlabUploader do
let(:uploader_class) { Class.new(described_class) }
- subject { uploader_class.new }
+ subject { uploader_class.new(double) }
describe '#file_storage?' do
context 'when file storage is used' do
diff --git a/vendor/ingress/values.yaml b/vendor/ingress/values.yaml
new file mode 100644
index 00000000000..cdb7da77e86
--- /dev/null
+++ b/vendor/ingress/values.yaml
@@ -0,0 +1,8 @@
+controller:
+ image:
+ tag: "0.10.2"
+ repository: "quay.io/kubernetes-ingress-controller/nginx-ingress-controller"
+ stats.enabled: true
+ podAnnotations:
+ prometheus.io/scrape: "true"
+ prometheus.io/port: "10254"
diff --git a/vendor/prometheus/values.yaml b/vendor/prometheus/values.yaml
index 5249449c7f8..db967514be7 100644
--- a/vendor/prometheus/values.yaml
+++ b/vendor/prometheus/values.yaml
@@ -2,7 +2,7 @@ alertmanager:
enabled: false
kubeStateMetrics:
- enabled: false
+ enabled: true
nodeExporter:
enabled: false
@@ -10,11 +10,15 @@ nodeExporter:
pushgateway:
enabled: false
+server:
+ image:
+ tag: v2.1.0
+
serverFiles:
- alerts: ""
- rules: ""
+ alerts: {}
+ rules: {}
- prometheus.yml: |-
+ prometheus.yml:
rule_files:
- /etc/config/rules
- /etc/config/alerts
@@ -26,92 +30,108 @@ serverFiles:
- job_name: kubernetes-cadvisor
scheme: https
tls_config:
- ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
+ ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
insecure_skip_verify: true
- bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
+ bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs:
- - role: node
- api_server: https://kubernetes.default.svc:443
- tls_config:
- ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
- bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
+ - role: node
relabel_configs:
- - action: labelmap
- regex: __meta_kubernetes_node_label_(.+)
- - target_label: __address__
- replacement: kubernetes.default.svc:443
- - source_labels:
- - __meta_kubernetes_node_name
- regex: "(.+)"
- target_label: __metrics_path__
- replacement: "/api/v1/nodes/${1}/proxy/metrics/cadvisor"
+ - action: labelmap
+ regex: __meta_kubernetes_node_label_(.+)
+ - target_label: __address__
+ replacement: kubernetes.default.svc:443
+ - source_labels:
+ - __meta_kubernetes_node_name
+ regex: "(.+)"
+ target_label: __metrics_path__
+ replacement: "/api/v1/nodes/${1}/proxy/metrics/cadvisor"
metric_relabel_configs:
- - source_labels:
- - pod_name
- target_label: environment
- regex: "(.+)-.+-.+"
+ - source_labels:
+ - pod_name
+ target_label: environment
+ regex: "(.+)-.+-.+"
+ - job_name: 'kubernetes-service-endpoints'
+ kubernetes_sd_configs:
+ - role: endpoints
+ relabel_configs:
+ - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
+ action: keep
+ regex: true
+ - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
+ action: replace
+ target_label: __scheme__
+ regex: (https?)
+ - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
+ action: replace
+ target_label: __metrics_path__
+ regex: (.+)
+ - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
+ action: replace
+ target_label: __address__
+ regex: (.+)(?::\d+);(\d+)
+ replacement: $1:$2
+ - action: labelmap
+ regex: __meta_kubernetes_service_label_(.+)
+ - source_labels: [__meta_kubernetes_namespace]
+ action: replace
+ target_label: kubernetes_namespace
+ - source_labels: [__meta_kubernetes_service_name]
+ action: replace
+ target_label: kubernetes_name
- job_name: kubernetes-nodes
scheme: https
tls_config:
- ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
+ ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
insecure_skip_verify: true
- bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
+ bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs:
- - role: node
- api_server: https://kubernetes.default.svc:443
- tls_config:
- ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
- bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
+ - role: node
relabel_configs:
- - action: labelmap
- regex: __meta_kubernetes_node_label_(.+)
- - target_label: __address__
- replacement: kubernetes.default.svc:443
- - source_labels:
- - __meta_kubernetes_node_name
- regex: "(.+)"
- target_label: __metrics_path__
- replacement: "/api/v1/nodes/${1}/proxy/metrics"
+ - action: labelmap
+ regex: __meta_kubernetes_node_label_(.+)
+ - target_label: __address__
+ replacement: kubernetes.default.svc:443
+ - source_labels:
+ - __meta_kubernetes_node_name
+ regex: "(.+)"
+ target_label: __metrics_path__
+ replacement: "/api/v1/nodes/${1}/proxy/metrics"
metric_relabel_configs:
- - source_labels:
- - pod_name
- target_label: environment
- regex: "(.+)-.+-.+"
+ - source_labels:
+ - pod_name
+ target_label: environment
+ regex: "(.+)-.+-.+"
- job_name: kubernetes-pods
tls_config:
- ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
+ ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
insecure_skip_verify: true
- bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
+ bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs:
- - role: pod
- api_server: https://kubernetes.default.svc:443
- tls_config:
- ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
- bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
+ - role: pod
relabel_configs:
- - source_labels:
- - __meta_kubernetes_pod_annotation_prometheus_io_scrape
- action: keep
- regex: 'true'
- - source_labels:
- - __meta_kubernetes_pod_annotation_prometheus_io_path
- action: replace
- target_label: __metrics_path__
- regex: "(.+)"
- - source_labels:
- - __address__
- - __meta_kubernetes_pod_annotation_prometheus_io_port
- action: replace
- regex: "([^:]+)(?::[0-9]+)?;([0-9]+)"
- replacement: "$1:$2"
- target_label: __address__
- - action: labelmap
- regex: __meta_kubernetes_pod_label_(.+)
- - source_labels:
- - __meta_kubernetes_namespace
- action: replace
- target_label: kubernetes_namespace
- - source_labels:
- - __meta_kubernetes_pod_name
- action: replace
- target_label: kubernetes_pod_name
+ - source_labels:
+ - __meta_kubernetes_pod_annotation_prometheus_io_scrape
+ action: keep
+ regex: 'true'
+ - source_labels:
+ - __meta_kubernetes_pod_annotation_prometheus_io_path
+ action: replace
+ target_label: __metrics_path__
+ regex: "(.+)"
+ - source_labels:
+ - __address__
+ - __meta_kubernetes_pod_annotation_prometheus_io_port
+ action: replace
+ regex: "([^:]+)(?::[0-9]+)?;([0-9]+)"
+ replacement: "$1:$2"
+ target_label: __address__
+ - action: labelmap
+ regex: __meta_kubernetes_pod_label_(.+)
+ - source_labels:
+ - __meta_kubernetes_namespace
+ action: replace
+ target_label: kubernetes_namespace
+ - source_labels:
+ - __meta_kubernetes_pod_name
+ action: replace
+ target_label: kubernetes_pod_name