summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/memory.gitlab-ci.yml12
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml17
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/commons/polyfills.js1
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js26
-rw-r--r--app/assets/javascripts/ide/stores/actions.js4
-rw-r--r--app/models/clusters/applications/runner.rb2
-rw-r--r--app/views/projects/pages_domains/_form.html.haml2
-rw-r--r--app/workers/pages_domain_ssl_renewal_cron_worker.rb4
-rw-r--r--app/workers/pages_domain_ssl_renewal_worker.rb6
-rw-r--r--changelogs/unreleased/60860-keep-empty-folders-in-tree.yml5
-rw-r--r--changelogs/unreleased/gitaly-version-v1.49.0.yml5
-rw-r--r--changelogs/unreleased/mh-colon-autocomplete.yml5
-rw-r--r--changelogs/unreleased/sh-quiet-backup-secrets-log.yml5
-rw-r--r--changelogs/unreleased/sh-recover-ee-schema-backport-migration-failure.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-6-0.yml5
-rw-r--r--config/initializers/forbid_sidekiq_in_transactions.rb2
-rw-r--r--db/migrate/20190402150158_backport_enterprise_schema.rb48
-rw-r--r--db/migrate/20190513174947_enable_create_incident_issues_by_default.rb19
-rw-r--r--db/schema.rb6
-rw-r--r--doc/development/testing_guide/frontend_testing.md5
-rw-r--r--doc/user/application_security/dependency_scanning/index.md17
-rw-r--r--doc/user/project/integrations/prometheus.md2
-rw-r--r--lib/after_commit_queue.rb14
-rw-r--r--lib/gitlab/database.rb18
-rw-r--r--lib/gitlab/git/repository.rb5
-rw-r--r--lib/gitlab/lets_encrypt.rb16
-rw-r--r--lib/gitlab/lets_encrypt/client.rb10
-rw-r--r--lib/tasks/gitlab/backup.rake4
-rwxr-xr-xscripts/generate-gems-memory-metrics-static18
-rwxr-xr-xscripts/generate-gems-size-metrics-static30
-rwxr-xr-xscripts/memory-static20
-rwxr-xr-xscripts/memory-static-objects27
-rwxr-xr-xscripts/review_apps/review-apps.sh102
-rwxr-xr-xscripts/trigger-build9
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb70
-rw-r--r--spec/frontend/helpers/vuex_action_helper.js7
-rw-r--r--spec/frontend/helpers/vuex_action_helper_spec.js166
-rw-r--r--spec/javascripts/ide/stores/actions_spec.js27
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb18
-rw-r--r--spec/lib/gitlab/lets_encrypt/client_spec.rb36
-rw-r--r--spec/lib/gitlab/lets_encrypt_spec.rb56
-rw-r--r--spec/migrations/backport_enterprise_schema_spec.rb41
-rw-r--r--spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb13
-rw-r--r--spec/workers/pages_domain_ssl_renewal_worker_spec.rb19
45 files changed, 705 insertions, 226 deletions
diff --git a/.gitlab/ci/memory.gitlab-ci.yml b/.gitlab/ci/memory.gitlab-ci.yml
index 50b843df585..2f3907a331a 100644
--- a/.gitlab/ci/memory.gitlab-ci.yml
+++ b/.gitlab/ci/memory.gitlab-ci.yml
@@ -5,15 +5,15 @@ memory-static:
# Loads each of gems in the Gemfile and checks how much memory they consume when they are required.
# 'derailed_benchmarks' internally uses 'get_process_mem'
- - scripts/memory-static 'tmp/memory_static_full_report.txt' 'tmp/memory_static_metrics.txt'
+ - bundle exec derailed bundle:mem > tmp/memory_bundle_mem.txt
+ - scripts/generate-gems-size-metrics-static tmp/memory_bundle_mem.txt >> 'tmp/memory_metrics.txt'
# Outputs detailed information about objects created while gems are loaded.
# 'derailed_benchmarks' internally uses 'memory_profiler'
- - scripts/memory-static-objects 'tmp/memory_static_objects_full_report.txt' 'tmp/memory_static_metrics.txt'
+ - bundle exec derailed bundle:objects > tmp/memory_bundle_objects.txt
+ - scripts/generate-gems-memory-metrics-static tmp/memory_bundle_objects.txt >> 'tmp/memory_metrics.txt'
artifacts:
paths:
- - tmp/memory_static_full_report.txt
- - tmp/memory_static_objects_full_report.txt
- - tmp/memory_static_metrics.txt
+ - tmp/memory_*.txt
reports:
- metrics: tmp/memory_static_metrics.txt
+ metrics: tmp/memory_metrics.txt
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index 9b764028be9..933af90c85a 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -77,6 +77,7 @@ schedule:review-build-cng:
.review-deploy-base: &review-deploy-base
<<: *review-base
allow_failure: true
+ retry: 2
stage: review
variables:
HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
@@ -95,10 +96,16 @@ schedule:review-build-cng:
- install_api_client_dependencies_with_apk
- source scripts/review_apps/review-apps.sh
script:
- - perform_review_app_deployment
+ - check_kube_domain
+ - ensure_namespace
+ - install_tiller
+ - install_external_dns
+ - download_chart
+ - deploy || display_deployment_debug
+ - wait_for_review_app_to_be_accessible
+ - add_license
artifacts:
- paths:
- - review_app_url.txt
+ paths: [review_app_url.txt]
expire_in: 2 days
when: always
@@ -108,8 +115,6 @@ review-deploy:
schedule:review-deploy:
<<: *review-deploy-base
<<: *review-schedules-only
- script:
- - perform_review_app_deployment
review-stop:
<<: *review-base
@@ -124,11 +129,11 @@ review-stop:
script:
- source scripts/review_apps/review-apps.sh
- delete
- - cleanup
.review-qa-base: &review-qa-base
<<: *review-docker
allow_failure: true
+ retry: 2
stage: qa
variables:
<<: *review-docker-variables
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 9db5ea12f52..7f3a46a841e 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.48.0
+1.49.0
diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js
index d0cc4897aeb..a4394ab7e92 100644
--- a/app/assets/javascripts/commons/polyfills.js
+++ b/app/assets/javascripts/commons/polyfills.js
@@ -12,6 +12,7 @@ import 'core-js/es/promise/finally';
import 'core-js/es/string/code-point-at';
import 'core-js/es/string/from-code-point';
import 'core-js/es/string/includes';
+import 'core-js/es/string/starts-with';
import 'core-js/es/symbol';
import 'core-js/es/map';
import 'core-js/es/weak-map';
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 3b73dd83c9f..b308cd9c236 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -318,6 +318,7 @@ class GfmAutoComplete {
}
setupLabels($input) {
+ const instance = this;
const fetchData = this.fetchData.bind(this);
const LABEL_COMMAND = { LABEL: '/label', UNLABEL: '/unlabel', RELABEL: '/relabel' };
let command = '';
@@ -348,7 +349,6 @@ class GfmAutoComplete {
}));
},
matcher(flag, subtext) {
- const match = GfmAutoComplete.defaultMatcher(flag, subtext, this.app.controllers);
const subtextNodes = subtext
.split(/\n+/g)
.pop()
@@ -366,6 +366,27 @@ class GfmAutoComplete {
return null;
});
+ // If any label matches the inserted text after the last `~`, suggest those labels,
+ // even if any spaces or funky characters were typed.
+ // This allows matching labels like "Accepting merge requests".
+ const labels = instance.cachedData[flag];
+ if (labels) {
+ if (!subtext.includes(flag)) {
+ // Do not match if there is no `~` before the cursor
+ return null;
+ }
+ const lastCandidate = subtext.split(flag).pop();
+ if (labels.find(label => label.title.startsWith(lastCandidate))) {
+ return lastCandidate;
+ }
+ } else {
+ // Load all labels into the autocompleter.
+ // This needs to happen if e.g. editing a label in an existing comment, because normally
+ // label data would only be loaded only once you type `~`.
+ fetchData(this.$inputor, this.at);
+ }
+
+ const match = GfmAutoComplete.defaultMatcher(flag, subtext, this.app.controllers);
return match && match.length ? match[1] : null;
},
filter(query, data, searchKey) {
@@ -563,8 +584,9 @@ class GfmAutoComplete {
const accentAChar = decodeURI('%C3%80');
const accentYChar = decodeURI('%C3%BF');
+ // Holy regex, batman!
const regexp = new RegExp(
- `^(?:\\B|[^a-zA-Z0-9_\`${atSymbolsWithoutBar}]|\\s)${resultantFlag}(?!${atSymbolsWithBar})((?:[A-Za-z${accentAChar}-${accentYChar}0-9_'.+-]|[^\\x00-\\x7a])*)$`,
+ `^(?:\\B|[^a-zA-Z0-9_\`${atSymbolsWithoutBar}]|\\s)${resultantFlag}(?!${atSymbolsWithBar})((?:[A-Za-z${accentAChar}-${accentYChar}0-9_'.+-:]|[^\\x00-\\x7a])*)$`,
'gi',
);
diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js
index 5429b834708..48aabaf9dcf 100644
--- a/app/assets/javascripts/ide/stores/actions.js
+++ b/app/assets/javascripts/ide/stores/actions.js
@@ -208,10 +208,6 @@ export const deleteEntry = ({ commit, dispatch, state }, path) => {
commit(types.DELETE_ENTRY, path);
- if (entry.parentPath && state.entries[entry.parentPath].tree.length === 0) {
- dispatch('deleteEntry', entry.parentPath);
- }
-
dispatch('triggerFilesChange');
};
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index db7fd8524c2..f0256ff4d41 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ApplicationRecord
- VERSION = '0.5.2'.freeze
+ VERSION = '0.6.0'.freeze
self.table_name = 'clusters_applications_runners'
diff --git a/app/views/projects/pages_domains/_form.html.haml b/app/views/projects/pages_domains/_form.html.haml
index e7edb93f05b..5b657966909 100644
--- a/app/views/projects/pages_domains/_form.html.haml
+++ b/app/views/projects/pages_domains/_form.html.haml
@@ -11,7 +11,7 @@
- if Gitlab.config.pages.external_https
- - auto_ssl_available = ::Gitlab::LetsEncrypt::Client.new.enabled?
+ - auto_ssl_available = ::Gitlab::LetsEncrypt.enabled?(@domain)
- auto_ssl_enabled = @domain.auto_ssl_enabled?
- auto_ssl_available_and_enabled = auto_ssl_available && auto_ssl_enabled
diff --git a/app/workers/pages_domain_ssl_renewal_cron_worker.rb b/app/workers/pages_domain_ssl_renewal_cron_worker.rb
index 4ca9db922b4..40c34d29970 100644
--- a/app/workers/pages_domain_ssl_renewal_cron_worker.rb
+++ b/app/workers/pages_domain_ssl_renewal_cron_worker.rb
@@ -5,9 +5,9 @@ class PagesDomainSslRenewalCronWorker
include CronjobQueue
def perform
- return unless ::Gitlab::LetsEncrypt::Client.new.enabled?
-
PagesDomain.need_auto_ssl_renewal.find_each do |domain|
+ next unless ::Gitlab::LetsEncrypt.enabled?(domain)
+
PagesDomainSslRenewalWorker.perform_async(domain.id)
end
end
diff --git a/app/workers/pages_domain_ssl_renewal_worker.rb b/app/workers/pages_domain_ssl_renewal_worker.rb
index 00c9c4782d8..b32458ca777 100644
--- a/app/workers/pages_domain_ssl_renewal_worker.rb
+++ b/app/workers/pages_domain_ssl_renewal_worker.rb
@@ -4,11 +4,9 @@ class PagesDomainSslRenewalWorker
include ApplicationWorker
def perform(domain_id)
- return unless ::Gitlab::LetsEncrypt::Client.new.enabled?
-
domain = PagesDomain.find_by_id(domain_id)
-
- return unless domain
+ return unless domain&.enabled?
+ return unless ::Gitlab::LetsEncrypt.enabled?(domain)
::PagesDomains::ObtainLetsEncryptCertificateService.new(domain).execute
end
diff --git a/changelogs/unreleased/60860-keep-empty-folders-in-tree.yml b/changelogs/unreleased/60860-keep-empty-folders-in-tree.yml
new file mode 100644
index 00000000000..237d0fd6aef
--- /dev/null
+++ b/changelogs/unreleased/60860-keep-empty-folders-in-tree.yml
@@ -0,0 +1,5 @@
+---
+title: Keep the empty folders in the tree
+merge_request: 29196
+author:
+type: fixed
diff --git a/changelogs/unreleased/gitaly-version-v1.49.0.yml b/changelogs/unreleased/gitaly-version-v1.49.0.yml
new file mode 100644
index 00000000000..8795bab0209
--- /dev/null
+++ b/changelogs/unreleased/gitaly-version-v1.49.0.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade to Gitaly v1.49.0
+merge_request: 29990
+author:
+type: changed
diff --git a/changelogs/unreleased/mh-colon-autocomplete.yml b/changelogs/unreleased/mh-colon-autocomplete.yml
new file mode 100644
index 00000000000..8b169c22588
--- /dev/null
+++ b/changelogs/unreleased/mh-colon-autocomplete.yml
@@ -0,0 +1,5 @@
+---
+title: Allow auto-completing scoped labels
+merge_request: 29749
+author:
+type: added
diff --git a/changelogs/unreleased/sh-quiet-backup-secrets-log.yml b/changelogs/unreleased/sh-quiet-backup-secrets-log.yml
new file mode 100644
index 00000000000..cf3e90c0cb1
--- /dev/null
+++ b/changelogs/unreleased/sh-quiet-backup-secrets-log.yml
@@ -0,0 +1,5 @@
+---
+title: Silence backup warnings when CRON=1 in use
+merge_request: 30033
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-recover-ee-schema-backport-migration-failure.yml b/changelogs/unreleased/sh-recover-ee-schema-backport-migration-failure.yml
new file mode 100644
index 00000000000..1695e2827eb
--- /dev/null
+++ b/changelogs/unreleased/sh-recover-ee-schema-backport-migration-failure.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent EE backport migrations from running if CE is not migrated
+merge_request: 30002
+author:
+type: fixed
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-6-0.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-6-0.yml
new file mode 100644
index 00000000000..6719fa94b19
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-6-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Runner Helm Chart to 0.6.0
+merge_request: 29982
+author:
+type: other
diff --git a/config/initializers/forbid_sidekiq_in_transactions.rb b/config/initializers/forbid_sidekiq_in_transactions.rb
index deb94d7dbce..a69f1ba090e 100644
--- a/config/initializers/forbid_sidekiq_in_transactions.rb
+++ b/config/initializers/forbid_sidekiq_in_transactions.rb
@@ -17,7 +17,7 @@ module Sidekiq
module NoEnqueueingFromTransactions
%i(perform_async perform_at perform_in).each do |name|
define_method(name) do |*args|
- if !Sidekiq::Worker.skip_transaction_check && AfterCommitQueue.inside_transaction?
+ if !Sidekiq::Worker.skip_transaction_check && Gitlab::Database.inside_transaction?
begin
raise Sidekiq::Worker::EnqueueFromTransactionError, <<~MSG
`#{self}.#{name}` cannot be called inside a transaction as this can lead to
diff --git a/db/migrate/20190402150158_backport_enterprise_schema.rb b/db/migrate/20190402150158_backport_enterprise_schema.rb
index 610a8808383..8762cc53ed7 100644
--- a/db/migrate/20190402150158_backport_enterprise_schema.rb
+++ b/db/migrate/20190402150158_backport_enterprise_schema.rb
@@ -117,6 +117,8 @@ class BackportEnterpriseSchema < ActiveRecord::Migration[5.0]
end
def up
+ check_schema!
+
create_missing_tables
update_appearances
@@ -868,6 +870,52 @@ class BackportEnterpriseSchema < ActiveRecord::Migration[5.0]
remove_column_if_exists(:geo_nodes, :internal_url)
end
+ # Some users may have upgraded to EE at some point but downgraded to
+ # CE v11.11.3. As a result, their EE tables may not be in the right
+ # state. Here we check for these such cases and attempt to guide the
+ # user into recovering from this state by upgrading to v11.11.3 EE
+ # before installing v12.0.0 CE.
+ def check_schema!
+ # The following cases will fail later when this migration attempts
+ # to add a foreign key for non-existent columns.
+ columns_to_check = [
+ [:epics, :parent_id], # Added in GitLab 11.7
+ [:geo_event_log, :cache_invalidation_event_id], # Added in GitLab 11.4
+ [:vulnerability_feedback, :merge_request_id] # Added in GitLab 11.9
+ ].freeze
+
+ columns_to_check.each do |table, column|
+ check_ee_columns!(table, column)
+ end
+ end
+
+ def check_ee_columns!(table, column)
+ return unless table_exists?(table)
+ return if column_exists?(table, column)
+
+ raise_ee_migration_error!(table, column)
+ end
+
+ def raise_ee_migration_error!(table, column)
+ message = "Your database is missing the '#{column}' column from the '#{table}' table that is present for GitLab EE."
+
+ message +=
+ if ::Gitlab.ee?
+ "\nUpgrade your GitLab instance to 11.11.3 EE first!"
+ else
+ <<~MSG
+
+ Even though it looks like you're running a CE installation, it appears
+ you may have installed GitLab EE at some point. To migrate to GitLab 12.0:
+
+ 1. Install GitLab 11.11.3 EE
+ 2. Install GitLab 12.0.x CE
+ MSG
+ end
+
+ raise Exception.new(message)
+ end
+
def create_missing_tables
create_table_if_not_exists "approval_merge_request_rule_sources", id: :bigserial do |t|
t.bigint "approval_merge_request_rule_id", null: false
diff --git a/db/migrate/20190513174947_enable_create_incident_issues_by_default.rb b/db/migrate/20190513174947_enable_create_incident_issues_by_default.rb
new file mode 100644
index 00000000000..ecd466627fe
--- /dev/null
+++ b/db/migrate/20190513174947_enable_create_incident_issues_by_default.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class EnableCreateIncidentIssuesByDefault < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ change_default_for :create_issue, from: false, to: true
+ change_default_for :send_email, from: true, to: false
+ end
+
+ private
+
+ def change_default_for(column, from:, to:)
+ change_column_default :project_incident_management_settings,
+ column, from: from, to: to
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 44213fddd86..a94e5142627 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -2503,9 +2503,9 @@ ActiveRecord::Schema.define(version: 20190620112608) do
t.index ["project_id"], name: "index_project_import_data_on_project_id", using: :btree
end
- create_table "project_incident_management_settings", primary_key: "project_id", id: :integer, default: nil, force: :cascade do |t|
- t.boolean "create_issue", default: false, null: false
- t.boolean "send_email", default: true, null: false
+ create_table "project_incident_management_settings", primary_key: "project_id", id: :serial, force: :cascade do |t|
+ t.boolean "create_issue", default: true, null: false
+ t.boolean "send_email", default: false, null: false
t.text "issue_template_key"
end
diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md
index 4c9d1684c00..fc9b175bc8a 100644
--- a/doc/development/testing_guide/frontend_testing.md
+++ b/doc/development/testing_guide/frontend_testing.md
@@ -28,8 +28,13 @@ we need to solve before being able to use Jest for all our needs.
- Jest runs in a Node.js environment, not in a browser. Support for running Jest tests in a browser [is planned](https://gitlab.com/gitlab-org/gitlab-ce/issues/58205).
- Because Jest runs in a Node.js environment, it uses [jsdom](https://github.com/jsdom/jsdom) by default.
+- Jest does not have access to Webpack loaders or aliases.
+ The aliases used by Jest are defined in its [own config](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/jest.config.js).
- All calls to `setTimeout` and `setInterval` are mocked away. See also [Jest Timer Mocks](https://jestjs.io/docs/en/timer-mocks).
- `rewire` is not required because Jest supports mocking modules. See also [Manual Mocks](https://jestjs.io/docs/en/manual-mocks).
+- No [context object](https://jasmine.github.io/tutorials/your_first_suite#section-The_%3Ccode%3Ethis%3C/code%3E_keyword) is passed to tests in Jest.
+ This means sharing `this.something` between `beforeEach()` and `it()` for example does not work.
+ Instead you should declare shared variables in the context that they are needed (via `const` / `let`).
- The following will cause tests to fail in Jest:
- Unmocked requests.
- Unhandled Promise rejections.
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index dc86e66cb4f..ea8b96eb24d 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -46,13 +46,16 @@ this is enabled by default.
The following languages and dependency managers are supported.
-| Language (package managers) | Scan tool |
-|-----------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|
-| JavaScript ([npm](https://www.npmjs.com/), [yarn](https://yarnpkg.com/en/)) | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium/general), [Retire.js](https://retirejs.github.io/retire.js) |
-| Python ([pip](https://pip.pypa.io/en/stable/)) (only `requirements.txt` supported) | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium/general) |
-| Ruby ([gem](https://rubygems.org/)) | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium/general), [bundler-audit](https://github.com/rubysec/bundler-audit) |
-| Java ([Maven](https://maven.apache.org/)) | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium/general) |
-| PHP ([Composer](https://getcomposer.org/)) | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium/general) |
+| Language (package managers) | Supported | Scan tool(s) |
+|----------------------------- | --------- | ------------ |
+| JavaScript ([npm](https://www.npmjs.com/), [yarn](https://yarnpkg.com/en/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium), [Retire.js](https://retirejs.github.io/retire.js) |
+| Python ([pip](https://pip.pypa.io/en/stable/)) (only `requirements.txt` supported) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
+| Ruby ([gem](https://rubygems.org/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium), [bundler-audit](https://github.com/rubysec/bundler-audit) |
+| Java ([Maven](https://maven.apache.org/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
+| PHP ([Composer](https://getcomposer.org/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
+| Python ([poetry](https://poetry.eustace.io/)) | no ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/7006 "Support Poetry in Dependency Scanning")) | not available |
+| Python ([Pipfile](https://docs.pipenv.org/en/latest/basics/)) | no ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/11756 "Pipfile.lock support for Dependency Scanning"))| not available |
+| Go ([Golang](https://golang.org/)) | no ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/7132 "Dependency Scanning for Go")) | not available |
## Remote checks
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index 751e8e44e60..aab7131e353 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -160,7 +160,7 @@ receivers:
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/4925) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.11.
-Alerts can be used to trigger actions, like open an issue automatically. To configure the actions:
+Alerts can be used to trigger actions, like open an issue automatically (enabled by default since `12.1`). To configure the actions:
1. Navigate to your project's **Settings > Operations > Incidents**.
1. Enable the option to create issues.
diff --git a/lib/after_commit_queue.rb b/lib/after_commit_queue.rb
index 6fb7985f955..6a180fdf338 100644
--- a/lib/after_commit_queue.rb
+++ b/lib/after_commit_queue.rb
@@ -15,7 +15,7 @@ module AfterCommitQueue
end
def run_after_commit_or_now(&block)
- if AfterCommitQueue.inside_transaction?
+ if Gitlab::Database.inside_transaction?
if ActiveRecord::Base.connection.current_transaction.records.include?(self)
run_after_commit(&block)
else
@@ -32,18 +32,6 @@ module AfterCommitQueue
true
end
- def self.open_transactions_baseline
- if ::Rails.env.test?
- return DatabaseCleaner.connections.count { |conn| conn.strategy.is_a?(DatabaseCleaner::ActiveRecord::Transaction) }
- end
-
- 0
- end
-
- def self.inside_transaction?
- ActiveRecord::Base.connection.open_transactions > open_transactions_baseline
- end
-
protected
def _run_after_commit_queue
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index 8da98cc3909..e4d4779ba9a 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -234,6 +234,7 @@ module Gitlab
def self.connection
ActiveRecord::Base.connection
end
+ private_class_method :connection
def self.cached_column_exists?(table_name, column_name)
connection.schema_cache.columns_hash(table_name).has_key?(column_name.to_s)
@@ -243,8 +244,6 @@ module Gitlab
connection.schema_cache.data_source_exists?(table_name)
end
- private_class_method :connection
-
def self.database_version
row = connection.execute("SELECT VERSION()").first
@@ -272,5 +271,20 @@ module Gitlab
end
end
end
+
+ # inside_transaction? will return true if the caller is running within a transaction. Handles special cases
+ # when running inside a test environment, in which the entire test is running with a DatabaseCleaner transaction
+ def self.inside_transaction?
+ ActiveRecord::Base.connection.open_transactions > open_transactions_baseline
+ end
+
+ def self.open_transactions_baseline
+ if ::Rails.env.test?
+ return DatabaseCleaner.connections.count { |conn| conn.strategy.is_a?(DatabaseCleaner::ActiveRecord::Transaction) }
+ end
+
+ 0
+ end
+ private_class_method :open_transactions_baseline
end
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index a6739f12280..19b6aab1c4f 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -15,11 +15,6 @@ module Gitlab
SEARCH_CONTEXT_LINES = 3
REV_LIST_COMMIT_LIMIT = 2_000
- # In https://gitlab.com/gitlab-org/gitaly/merge_requests/698
- # We copied these two prefixes into gitaly-go, so don't change these
- # or things will break! (REBASE_WORKTREE_PREFIX and SQUASH_WORKTREE_PREFIX)
- REBASE_WORKTREE_PREFIX = 'rebase'.freeze
- SQUASH_WORKTREE_PREFIX = 'squash'.freeze
GITALY_INTERNAL_URL = 'ssh://gitaly/internal.git'.freeze
GITLAB_PROJECTS_TIMEOUT = Gitlab.config.gitlab_shell.git_timeout
EMPTY_REPOSITORY_CHECKSUM = '0000000000000000000000000000000000000000'.freeze
diff --git a/lib/gitlab/lets_encrypt.rb b/lib/gitlab/lets_encrypt.rb
new file mode 100644
index 00000000000..cdf24f24647
--- /dev/null
+++ b/lib/gitlab/lets_encrypt.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module LetsEncrypt
+ def self.enabled?(pages_domain = nil)
+ return false unless Gitlab::CurrentSettings.lets_encrypt_terms_of_service_accepted
+
+ return false unless Feature.enabled?(:pages_auto_ssl)
+
+ # If no domain is passed, just check whether we're enabled globally
+ return true unless pages_domain
+
+ !!pages_domain.project && Feature.enabled?(:pages_auto_ssl_for_project, pages_domain.project)
+ end
+ end
+end
diff --git a/lib/gitlab/lets_encrypt/client.rb b/lib/gitlab/lets_encrypt/client.rb
index 66aea137012..ad2921ed555 100644
--- a/lib/gitlab/lets_encrypt/client.rb
+++ b/lib/gitlab/lets_encrypt/client.rb
@@ -34,14 +34,6 @@ module Gitlab
acme_client.terms_of_service
end
- def enabled?
- return false unless Feature.enabled?(:pages_auto_ssl)
-
- return false unless private_key
-
- Gitlab::CurrentSettings.lets_encrypt_terms_of_service_accepted
- end
-
private
def acme_client
@@ -65,7 +57,7 @@ module Gitlab
end
def ensure_account
- raise 'Acme integration is disabled' unless enabled?
+ raise 'Acme integration is disabled' unless ::Gitlab::LetsEncrypt.enabled?
@acme_account ||= acme_client.new_account(contact: contact, terms_of_service_agreed: true)
end
diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake
index c531eb1d216..2bf71701b57 100644
--- a/lib/tasks/gitlab/backup.rake
+++ b/lib/tasks/gitlab/backup.rake
@@ -21,10 +21,10 @@ namespace :gitlab do
backup.cleanup
backup.remove_old
- puts "Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data \n" \
+ progress.puts "Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data \n" \
"and are not included in this backup. You will need these files to restore a backup.\n" \
"Please back them up manually.".color(:red)
- puts "Backup task is done."
+ progress.puts "Backup task is done."
end
# Restore backup of GitLab system
diff --git a/scripts/generate-gems-memory-metrics-static b/scripts/generate-gems-memory-metrics-static
new file mode 100755
index 00000000000..aa7ce3615bf
--- /dev/null
+++ b/scripts/generate-gems-memory-metrics-static
@@ -0,0 +1,18 @@
+#!/usr/bin/env ruby
+
+abort "usage: #{__FILE__} <memory_bundle_objects_file_name>" unless ARGV.length == 1
+memory_bundle_objects_file_name = ARGV.first
+
+full_report = File.readlines(memory_bundle_objects_file_name)
+
+allocated_str = full_report[1]
+retained_str = full_report[2]
+allocated_stats = /Total allocated: (?<bytes>.*) bytes \((?<objects>.*) objects\)/.match(allocated_str)
+retained_stats = /Total retained: (?<bytes>.*) bytes \((?<objects>.*) objects\)/.match(retained_str)
+
+abort 'failed to process the benchmark output' unless allocated_stats && retained_stats
+
+puts "memory_static_objects_allocated_mb #{(allocated_stats[:bytes].to_f / (1024 * 1024)).round(1)}"
+puts "memory_static_objects_retained_mb #{(retained_stats[:bytes].to_f / (1024 * 1024)).round(1)}"
+puts "memory_static_objects_allocated_items #{allocated_stats[:objects]}"
+puts "memory_static_objects_retained_items #{retained_stats[:objects]}"
diff --git a/scripts/generate-gems-size-metrics-static b/scripts/generate-gems-size-metrics-static
new file mode 100755
index 00000000000..ceec8aaccf1
--- /dev/null
+++ b/scripts/generate-gems-size-metrics-static
@@ -0,0 +1,30 @@
+#!/usr/bin/env ruby
+
+abort "usage: #{__FILE__} <memory_bundle_mem_file_name>" unless ARGV.length == 1
+memory_bundle_mem_file_name = ARGV.first
+
+full_report = File.readlines(memory_bundle_mem_file_name)
+
+def total_size(memory_bundle_mem_report)
+ stats = /TOP: (?<total_mibs_str>.*) MiB/.match(memory_bundle_mem_report.first)
+ abort 'failed to process the benchmark output' unless stats
+ "gem_total_size_mb #{stats[:total_mibs_str].to_f.round(1)}"
+end
+
+TOP_LEVEL_GEM_LOG_FORMAT = /^ (?<gem_name>\S.*):\s*(?<gem_size>\d[.\d]*)\s*MiB/.freeze
+def all_gems(memory_bundle_mem_report)
+ memory_bundle_mem_report.map do |line|
+ TOP_LEVEL_GEM_LOG_FORMAT.match(line)
+ end.compact
+end
+
+def gems_as_metrics(gems_match_data)
+ gems_match_data.map do |gem|
+ gem_name = gem[:gem_name]
+ gem_size_mb = gem[:gem_size].to_f.round(1)
+ "gem_size_mb{name=\"#{gem_name}\"} #{gem_size_mb}"
+ end
+end
+
+puts total_size(full_report)
+puts gems_as_metrics(all_gems(full_report)).sort(&:casecmp)
diff --git a/scripts/memory-static b/scripts/memory-static
deleted file mode 100755
index 54f147a7a91..00000000000
--- a/scripts/memory-static
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env ruby
-
-require_relative '../lib/gitlab/popen'
-
-full_report_filename, metrics_filename = ARGV
-abort 'usage: memory-static <full_report_filename> <metrics_filename>' unless full_report_filename && metrics_filename
-
-full_report, status = Gitlab::Popen.popen(%w(bundle exec derailed bundle:mem))
-abort 'failed to execute the benchmark' unless status.zero?
-
-File.open(full_report_filename, 'w') do |f|
- f.write(full_report)
-end
-
-stats = /TOP: (?<total_mibs_str>.*) MiB/.match(full_report.lines.first)
-abort 'failed to process the benchmark output' unless stats
-
-File.open(metrics_filename, 'a') do |f|
- f.puts "memory_static_total_mb #{stats[:total_mibs_str].to_f.round(1)}"
-end
diff --git a/scripts/memory-static-objects b/scripts/memory-static-objects
deleted file mode 100755
index 2ad38d9717c..00000000000
--- a/scripts/memory-static-objects
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env ruby
-
-require_relative '../lib/gitlab/popen'
-
-full_report_filename, metrics_filename = ARGV
-abort 'usage: memory-static-objects <full_report_filename> <metrics_filename>' unless full_report_filename && metrics_filename
-
-full_report, status = Gitlab::Popen.popen(%w(bundle exec derailed bundle:objects))
-abort 'failed to execute the benchmark' unless status.zero?
-
-File.open(full_report_filename, 'w') do |f|
- f.write(full_report)
-end
-
-allocated_str = full_report.lines[1]
-retained_str = full_report.lines[2]
-allocated_stats = /Total allocated: (?<bytes>.*) bytes \((?<objects>.*) objects\)/.match(allocated_str)
-retained_stats = /Total retained: (?<bytes>.*) bytes \((?<objects>.*) objects\)/.match(retained_str)
-
-abort 'failed to process the benchmark output' unless allocated_stats && retained_stats
-
-File.open(metrics_filename, 'a') do |f|
- f.puts "memory_static_objects_allocated_mb #{(allocated_stats[:bytes].to_f / (1024 * 1024)).round(1)}"
- f.puts "memory_static_objects_retained_mb #{(retained_stats[:bytes].to_f / (104 * 1024)).round(1)}"
- f.puts "memory_static_objects_allocated_items #{allocated_stats[:objects]}"
- f.puts "memory_static_objects_retained_items #{retained_stats[:objects]}"
-end
diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh
index 3bae2e08a6f..633ea28e96c 100755
--- a/scripts/review_apps/review-apps.sh
+++ b/scripts/review_apps/review-apps.sh
@@ -1,7 +1,7 @@
[[ "$TRACE" ]] && set -x
export TILLER_NAMESPACE="$KUBE_NAMESPACE"
-function deployExists() {
+function deploy_exists() {
local namespace="${1}"
local deploy="${2}"
echoinfo "Checking if ${deploy} exists in the ${namespace} namespace..." true
@@ -13,8 +13,7 @@ function deployExists() {
return $deploy_exists
}
-function previousDeployFailed() {
- set +e
+function previous_deploy_failed() {
local deploy="${1}"
echoinfo "Checking for previous deployment of ${deploy}" true
@@ -34,7 +33,6 @@ function previousDeployFailed() {
else
echoerr "Previous deployment NOT found."
fi
- set -e
return $status
}
@@ -51,49 +49,35 @@ function delete() {
helm delete --purge "$name"
}
-function cleanup() {
- if [ -z "$CI_ENVIRONMENT_SLUG" ]; then
- echoerr "No release given, aborting the delete!"
- return
- fi
-
- echoinfo "Cleaning up '$CI_ENVIRONMENT_SLUG'..." true
-
- kubectl -n "$KUBE_NAMESPACE" delete \
- ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa \
- --now --ignore-not-found --include-uninitialized \
- -l release="$CI_ENVIRONMENT_SLUG"
-}
-
function get_pod() {
local app_name="${1}"
local status="${2-Running}"
get_pod_cmd="kubectl get pods -n ${KUBE_NAMESPACE} --field-selector=status.phase=${status} -lapp=${app_name},release=${CI_ENVIRONMENT_SLUG} --no-headers -o=custom-columns=NAME:.metadata.name"
- echoinfo "Running '${get_pod_cmd}'" true
+ echoinfo "Waiting till '${app_name}' pod is ready" true
+ echoinfo "Running '${get_pod_cmd}'"
+ local interval=5
+ local elapsed_seconds=0
+ local max_seconds=$((2 * 60))
while true; do
local pod_name
pod_name="$(eval "${get_pod_cmd}")"
[[ "${pod_name}" == "" ]] || break
- echoinfo "Waiting till '${app_name}' pod is ready";
- sleep 5;
+ if [[ "${elapsed_seconds}" -gt "${max_seconds}" ]]; then
+ echoerr "The pod name couldn't be found after ${elapsed_seconds} seconds, aborting."
+ break
+ fi
+
+ printf "."
+ let "elapsed_seconds+=interval"
+ sleep ${interval}
done
echoinfo "The pod name is '${pod_name}'."
echo "${pod_name}"
}
-function perform_review_app_deployment() {
- check_kube_domain
- ensure_namespace
- install_tiller
- install_external_dns
- time deploy
- wait_for_review_app_to_be_accessible
- add_license
-}
-
function check_kube_domain() {
echoinfo "Checking that Kube domain exists..." true
@@ -119,9 +103,16 @@ function install_tiller() {
echoinfo "Initiating the Helm client..."
helm init --client-only
+ # Set toleration for Tiller to be installed on a specific node pool
helm init \
+ --wait \
--upgrade \
- --replicas 2
+ --node-selectors "app=helm" \
+ --replicas 3 \
+ --override "spec.template.spec.tolerations[0].key"="dedicated" \
+ --override "spec.template.spec.tolerations[0].operator"="Equal" \
+ --override "spec.template.spec.tolerations[0].value"="helm" \
+ --override "spec.template.spec.tolerations[0].effect"="NoSchedule"
kubectl rollout status -n "$TILLER_NAMESPACE" -w "deployment/tiller-deploy"
@@ -137,7 +128,7 @@ function install_external_dns() {
domain=$(echo "${REVIEW_APPS_DOMAIN}" | awk -F. '{printf "%s.%s", $(NF-1), $NF}')
echoinfo "Installing external DNS for domain ${domain}..." true
- if ! deployExists "${KUBE_NAMESPACE}" "${release_name}" || previousDeployFailed "${release_name}" ; then
+ if ! deploy_exists "${KUBE_NAMESPACE}" "${release_name}" || previous_deploy_failed "${release_name}" ; then
echoinfo "Installing external-dns Helm chart"
helm repo update
helm install stable/external-dns \
@@ -156,7 +147,7 @@ function install_external_dns() {
fi
}
-function create_secret() {
+function create_application_secret() {
echoinfo "Creating the ${CI_ENVIRONMENT_SLUG}-gitlab-initial-root-password secret in the ${KUBE_NAMESPACE} namespace..." true
kubectl create secret generic -n "$KUBE_NAMESPACE" \
@@ -165,7 +156,7 @@ function create_secret() {
--dry-run -o json | kubectl apply -f -
}
-function download_gitlab_chart() {
+function download_chart() {
echoinfo "Downloading the GitLab chart..." true
curl -o gitlab.tar.bz2 "https://gitlab.com/charts/gitlab/-/archive/${GITLAB_HELM_CHART_REF}/gitlab-${GITLAB_HELM_CHART_REF}.tar.bz2"
@@ -194,14 +185,12 @@ function deploy() {
gitlab_workhorse_image_repository="${IMAGE_REPOSITORY}/gitlab-workhorse-${IMAGE_VERSION}"
# Cleanup and previous installs, as FAILED and PENDING_UPGRADE will cause errors with `upgrade`
- if [ "$CI_ENVIRONMENT_SLUG" != "production" ] && previousDeployFailed "$CI_ENVIRONMENT_SLUG" ; then
+ if [ "$CI_ENVIRONMENT_SLUG" != "production" ] && previous_deploy_failed "$CI_ENVIRONMENT_SLUG" ; then
echo "Deployment in bad state, cleaning up $CI_ENVIRONMENT_SLUG"
delete
- cleanup
fi
- create_secret
- download_gitlab_chart
+ create_application_secret
HELM_CMD=$(cat << EOF
helm upgrade --install \
@@ -216,7 +205,7 @@ HELM_CMD=$(cat << EOF
--set prometheus.install=false \
--set global.ingress.configureCertmanager=false \
--set global.ingress.tls.secretName=tls-cert \
- --set global.ingress.annotations."external-dns\.alpha\.kubernetes\.io/ttl"="10"
+ --set global.ingress.annotations."external-dns\.alpha\.kubernetes\.io/ttl"="10" \
--set nginx-ingress.controller.service.enableHttp=false \
--set nginx-ingress.defaultBackend.resources.requests.memory=7Mi \
--set nginx-ingress.controller.resources.requests.memory=440M \
@@ -252,14 +241,35 @@ EOF
echoinfo "Deploying with:"
echoinfo "${HELM_CMD}"
- eval $HELM_CMD || true
+ eval "${HELM_CMD}"
+}
+
+function display_deployment_debug() {
+ migrations_pod=$(get_pod "migrations");
+ if [ -z "${migrations_pod}" ]; then
+ echoerr "Migrations pod not found."
+ else
+ echoinfo "Logs tail of the ${migrations_pod} pod..."
+
+ kubectl logs -n "$KUBE_NAMESPACE" "${migrations_pod}" | sed "s/${REVIEW_APPS_ROOT_PASSWORD}/[REDACTED]/g"
+ fi
+
+ unicorn_pod=$(get_pod "unicorn");
+ if [ -z "${unicorn_pod}" ]; then
+ echoerr "Unicorn pod not found."
+ else
+ echoinfo "Logs tail of the ${unicorn_pod} pod..."
+
+ kubectl logs -n "$KUBE_NAMESPACE" -c unicorn "${unicorn_pod}" | sed "s/${REVIEW_APPS_ROOT_PASSWORD}/[REDACTED]/g"
+ fi
}
function wait_for_review_app_to_be_accessible() {
- # In case the Review App isn't completely available yet. Keep trying for 5 minutes.
+ echoinfo "Waiting for the Review App at ${CI_ENVIRONMENT_URL} to be accessible..." true
+
local interval=5
local elapsed_seconds=0
- local max_seconds=$((5 * 60))
+ local max_seconds=$((2 * 60))
while true; do
local review_app_http_code
review_app_http_code=$(curl --silent --output /dev/null --max-time 5 --write-out "%{http_code}" "${CI_ENVIRONMENT_URL}/users/sign_in")
@@ -272,10 +282,10 @@ function wait_for_review_app_to_be_accessible() {
sleep ${interval}
done
- if [[ "${review_app_http_code}" == "200" ]]; then
- echoinfo "The Review App at ${CI_ENVIRONMENT_URL} is ready!"
+ if [[ "${review_app_http_code}" -eq "200" ]]; then
+ echoinfo "The Review App at ${CI_ENVIRONMENT_URL} is ready after ${elapsed_seconds} seconds!"
else
- echoerr "The Review App at ${CI_ENVIRONMENT_URL} isn't ready after 5 minutes of polling..."
+ echoerr "The Review App at ${CI_ENVIRONMENT_URL} isn't ready after ${max_seconds} seconds of polling..."
exit 1
fi
}
diff --git a/scripts/trigger-build b/scripts/trigger-build
index 52bc61cac56..4d8110fce10 100755
--- a/scripts/trigger-build
+++ b/scripts/trigger-build
@@ -122,7 +122,14 @@ module Trigger
end
def ref
- ENV['CNG_BRANCH'] || 'master'
+ default_ref =
+ if ENV['CI_COMMIT_REF_NAME'] =~ /^[\d-]+-stable(-ee)?$/
+ ENV['CI_COMMIT_REF_NAME']
+ else
+ 'master'
+ end
+
+ ENV['CNG_BRANCH'] || default_ref
end
def trigger_token
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 8eb413bdd8d..40845ec48f9 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -3,14 +3,14 @@ require 'rails_helper'
describe 'GFM autocomplete', :js do
let(:issue_xss_title) { 'This will execute alert<img src=x onerror=alert(2)&lt;img src=x onerror=alert(1)&gt;' }
let(:user_xss_title) { 'eve <img src=x onerror=alert(2)&lt;img src=x onerror=alert(1)&gt;' }
- let(:label_xss_title) { 'alert label &lt;img src=x onerror="alert(\'Hello xss\');" a'}
+ let(:label_xss_title) { 'alert label &lt;img src=x onerror="alert(\'Hello xss\');" a' }
let(:milestone_xss_title) { 'alert milestone &lt;img src=x onerror="alert(\'Hello xss\');" a' }
let(:user_xss) { create(:user, name: user_xss_title, username: 'xss.user') }
- let(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') }
+ let(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') }
let(:project) { create(:project) }
let(:label) { create(:label, project: project, title: 'special+') }
- let(:issue) { create(:issue, project: project) }
+ let(:issue) { create(:issue, project: project) }
before do
project.add_maintainer(user)
@@ -293,6 +293,70 @@ describe 'GFM autocomplete', :js do
expect(find('.atwho-view-ul').text).to have_content('alert label')
end
end
+
+ it 'allows colons when autocompleting scoped labels' do
+ create(:label, project: project, title: 'scoped:label')
+
+ note = find('#note-body')
+ type(note, '~scoped:')
+
+ wait_for_requests
+
+ page.within '.atwho-container #at-view-labels' do
+ expect(find('.atwho-view-ul').text).to have_content('scoped:label')
+ end
+ end
+
+ it 'allows colons when autocompleting scoped labels with double colons' do
+ create(:label, project: project, title: 'scoped::label')
+
+ note = find('#note-body')
+ type(note, '~scoped::')
+
+ wait_for_requests
+
+ page.within '.atwho-container #at-view-labels' do
+ expect(find('.atwho-view-ul').text).to have_content('scoped::label')
+ end
+ end
+
+ it 'allows spaces when autocompleting multi-word labels' do
+ create(:label, project: project, title: 'Accepting merge requests')
+
+ note = find('#note-body')
+ type(note, '~Accepting merge')
+
+ wait_for_requests
+
+ page.within '.atwho-container #at-view-labels' do
+ expect(find('.atwho-view-ul').text).to have_content('Accepting merge requests')
+ end
+ end
+
+ it 'only autocompletes the latest label' do
+ create(:label, project: project, title: 'Accepting merge requests')
+ create(:label, project: project, title: 'Accepting job applicants')
+
+ note = find('#note-body')
+ type(note, '~Accepting merge requests foo bar ~Accepting job')
+
+ wait_for_requests
+
+ page.within '.atwho-container #at-view-labels' do
+ expect(find('.atwho-view-ul').text).to have_content('Accepting job applicants')
+ end
+ end
+
+ it 'does not autocomplete labels if no tilde is typed' do
+ create(:label, project: project, title: 'Accepting merge requests')
+
+ note = find('#note-body')
+ type(note, 'Accepting merge')
+
+ wait_for_requests
+
+ expect(page).not_to have_css('.atwho-container #at-view-labels')
+ end
end
shared_examples 'autocomplete suggestions' do
diff --git a/spec/frontend/helpers/vuex_action_helper.js b/spec/frontend/helpers/vuex_action_helper.js
index 88652202a8e..6c3569a2247 100644
--- a/spec/frontend/helpers/vuex_action_helper.js
+++ b/spec/frontend/helpers/vuex_action_helper.js
@@ -20,7 +20,7 @@ const noop = () => {};
* // expected mutations
* [
* { type: types.MUTATION}
- * { type: types.MUTATION_1, payload: jasmine.any(Number)}
+ * { type: types.MUTATION_1, payload: expect.any(Number)}
* ],
* // expected actions
* [
@@ -89,10 +89,7 @@ export default (
payload,
);
- return new Promise(resolve => {
- setImmediate(resolve);
- })
- .then(() => result)
+ return (result || new Promise(resolve => setImmediate(resolve)))
.catch(error => {
validateResults();
throw error;
diff --git a/spec/frontend/helpers/vuex_action_helper_spec.js b/spec/frontend/helpers/vuex_action_helper_spec.js
new file mode 100644
index 00000000000..61d05762a04
--- /dev/null
+++ b/spec/frontend/helpers/vuex_action_helper_spec.js
@@ -0,0 +1,166 @@
+import MockAdapter from 'axios-mock-adapter';
+import { TEST_HOST } from 'helpers/test_constants';
+import axios from '~/lib/utils/axios_utils';
+import testAction from './vuex_action_helper';
+
+describe('VueX test helper (testAction)', () => {
+ let originalExpect;
+ let assertion;
+ let mock;
+ const noop = () => {};
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ /**
+ * In order to test the helper properly, we need to overwrite the Jest
+ * `expect` helper. We test that the testAction helper properly passes the
+ * dispatched actions/committed mutations to the Jest helper.
+ */
+ originalExpect = expect;
+ assertion = null;
+ global.expect = actual => ({
+ toEqual: () => {
+ originalExpect(actual).toEqual(assertion);
+ },
+ });
+ });
+
+ afterEach(() => {
+ mock.restore();
+ global.expect = originalExpect;
+ });
+
+ it('properly passes state and payload to action', () => {
+ const exampleState = { FOO: 12, BAR: 3 };
+ const examplePayload = { BAZ: 73, BIZ: 55 };
+
+ const action = ({ state }, payload) => {
+ originalExpect(state).toEqual(exampleState);
+ originalExpect(payload).toEqual(examplePayload);
+ };
+
+ assertion = { mutations: [], actions: [] };
+
+ testAction(action, examplePayload, exampleState);
+ });
+
+ describe('given a sync action', () => {
+ it('mocks committing mutations', () => {
+ const action = ({ commit }) => {
+ commit('MUTATION');
+ };
+
+ assertion = { mutations: [{ type: 'MUTATION' }], actions: [] };
+
+ testAction(action, null, {}, assertion.mutations, assertion.actions, noop);
+ });
+
+ it('mocks dispatching actions', () => {
+ const action = ({ dispatch }) => {
+ dispatch('ACTION');
+ };
+
+ assertion = { actions: [{ type: 'ACTION' }], mutations: [] };
+
+ testAction(action, null, {}, assertion.mutations, assertion.actions, noop);
+ });
+
+ it('works with done callback once finished', done => {
+ assertion = { mutations: [], actions: [] };
+
+ testAction(noop, null, {}, assertion.mutations, assertion.actions, done);
+ });
+
+ it('returns a promise', done => {
+ assertion = { mutations: [], actions: [] };
+
+ testAction(noop, null, {}, assertion.mutations, assertion.actions)
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('given an async action (returning a promise)', () => {
+ let lastError;
+ const data = { FOO: 'BAR' };
+
+ const asyncAction = ({ commit, dispatch }) => {
+ dispatch('ACTION');
+
+ return axios
+ .get(TEST_HOST)
+ .catch(error => {
+ commit('ERROR');
+ lastError = error;
+ throw error;
+ })
+ .then(() => {
+ commit('SUCCESS');
+ return data;
+ });
+ };
+
+ beforeEach(() => {
+ lastError = null;
+ });
+
+ it('works with done callback once finished', done => {
+ mock.onGet(TEST_HOST).replyOnce(200, 42);
+
+ assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] };
+
+ testAction(asyncAction, null, {}, assertion.mutations, assertion.actions, done);
+ });
+
+ it('returns original data of successful promise while checking actions/mutations', done => {
+ mock.onGet(TEST_HOST).replyOnce(200, 42);
+
+ assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] };
+
+ testAction(asyncAction, null, {}, assertion.mutations, assertion.actions)
+ .then(res => {
+ originalExpect(res).toEqual(data);
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('returns original error of rejected promise while checking actions/mutations', done => {
+ mock.onGet(TEST_HOST).replyOnce(500, '');
+
+ assertion = { mutations: [{ type: 'ERROR' }], actions: [{ type: 'ACTION' }] };
+
+ testAction(asyncAction, null, {}, assertion.mutations, assertion.actions)
+ .then(done.fail)
+ .catch(error => {
+ originalExpect(error).toBe(lastError);
+ done();
+ });
+ });
+ });
+
+ it('works with async actions not returning promises', done => {
+ const data = { FOO: 'BAR' };
+
+ const asyncAction = ({ commit, dispatch }) => {
+ dispatch('ACTION');
+
+ axios
+ .get(TEST_HOST)
+ .then(() => {
+ commit('SUCCESS');
+ return data;
+ })
+ .catch(error => {
+ commit('ERROR');
+ throw error;
+ });
+ };
+
+ mock.onGet(TEST_HOST).replyOnce(200, 42);
+
+ assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] };
+
+ testAction(asyncAction, null, {}, assertion.mutations, assertion.actions, done);
+ });
+});
diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js
index 37354283cab..537152f5eed 100644
--- a/spec/javascripts/ide/stores/actions_spec.js
+++ b/spec/javascripts/ide/stores/actions_spec.js
@@ -492,6 +492,33 @@ describe('Multi-file store actions', () => {
done,
);
});
+
+ it('does not delete a folder after it is emptied', done => {
+ const testFolder = {
+ type: 'tree',
+ tree: [],
+ };
+ const testEntry = {
+ path: 'testFolder/entry-to-delete',
+ parentPath: 'testFolder',
+ opened: false,
+ tree: [],
+ };
+ testFolder.tree.push(testEntry);
+ store.state.entries = {
+ testFolder,
+ 'testFolder/entry-to-delete': testEntry,
+ };
+
+ testAction(
+ deleteEntry,
+ 'testFolder/entry-to-delete',
+ store.state,
+ [{ type: types.DELETE_ENTRY, payload: 'testFolder/entry-to-delete' }],
+ [{ type: 'burstUnusedSeal' }, { type: 'triggerFilesChange' }],
+ done,
+ );
+ });
});
describe('renameEntry', () => {
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index e72fb9c6fbc..cceeae8afe6 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -2038,24 +2038,24 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#clean_stale_repository_files' do
- let(:worktree_path) { File.join(repository_path, 'worktrees', 'delete-me') }
+ let(:worktree_id) { 'rebase-1' }
+ let(:gitlab_worktree_path) { File.join(repository_path, 'gitlab-worktree', worktree_id) }
+ let(:admin_dir) { File.join(repository_path, 'worktrees') }
it 'cleans up the files' do
- create_worktree = %W[git -C #{repository_path} worktree add --detach #{worktree_path} master]
+ create_worktree = %W[git -C #{repository_path} worktree add --detach #{gitlab_worktree_path} master]
raise 'preparation failed' unless system(*create_worktree, err: '/dev/null')
- FileUtils.touch(worktree_path, mtime: Time.now - 8.hours)
+ FileUtils.touch(gitlab_worktree_path, mtime: Time.now - 8.hours)
# git rev-list --all will fail in git 2.16 if HEAD is pointing to a non-existent object,
# but the HEAD must be 40 characters long or git will ignore it.
- File.write(File.join(worktree_path, 'HEAD'), Gitlab::Git::BLANK_SHA)
-
- # git 2.16 fails with "fatal: bad object HEAD"
- expect(rev_list_all).to be false
+ File.write(File.join(admin_dir, worktree_id, 'HEAD'), Gitlab::Git::BLANK_SHA)
+ expect(rev_list_all).to be(false)
repository.clean_stale_repository_files
- expect(rev_list_all).to be true
- expect(File.exist?(worktree_path)).to be_falsey
+ expect(rev_list_all).to be(true)
+ expect(File.exist?(gitlab_worktree_path)).to be_falsey
end
def rev_list_all
diff --git a/spec/lib/gitlab/lets_encrypt/client_spec.rb b/spec/lib/gitlab/lets_encrypt/client_spec.rb
index 5454d9c1af4..cbb862cb0c9 100644
--- a/spec/lib/gitlab/lets_encrypt/client_spec.rb
+++ b/spec/lib/gitlab/lets_encrypt/client_spec.rb
@@ -116,42 +116,6 @@ describe ::Gitlab::LetsEncrypt::Client do
end
end
- describe '#enabled?' do
- subject { client.enabled? }
-
- context 'when terms of service are accepted' do
- it { is_expected.to eq(true) }
-
- context "when private_key isn't present and database is read only" do
- before do
- allow(::Gitlab::Database).to receive(:read_only?).and_return(true)
- end
-
- it 'returns false' do
- expect(::Gitlab::CurrentSettings.lets_encrypt_private_key).to eq(nil)
-
- is_expected.to eq(false)
- end
- end
-
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(pages_auto_ssl: false)
- end
-
- it { is_expected.to eq(false) }
- end
- end
-
- context 'when terms of service are not accepted' do
- before do
- stub_application_setting(lets_encrypt_terms_of_service_accepted: false)
- end
-
- it { is_expected.to eq(false) }
- end
- end
-
describe '#terms_of_service_url' do
subject { client.terms_of_service_url }
diff --git a/spec/lib/gitlab/lets_encrypt_spec.rb b/spec/lib/gitlab/lets_encrypt_spec.rb
new file mode 100644
index 00000000000..674b114e9d3
--- /dev/null
+++ b/spec/lib/gitlab/lets_encrypt_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ::Gitlab::LetsEncrypt do
+ include LetsEncryptHelpers
+
+ before do
+ stub_lets_encrypt_settings
+ end
+
+ describe '.enabled?' do
+ let(:project) { create(:project) }
+ let(:pages_domain) { create(:pages_domain, project: project) }
+
+ subject { described_class.enabled?(pages_domain) }
+
+ context 'when terms of service are accepted' do
+ it { is_expected.to eq(true) }
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(pages_auto_ssl: false)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
+ context 'when terms of service are not accepted' do
+ before do
+ stub_application_setting(lets_encrypt_terms_of_service_accepted: false)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when feature flag for project is disabled' do
+ before do
+ stub_feature_flags(pages_auto_ssl_for_project: false)
+ end
+
+ it 'returns false' do
+ is_expected.to eq(false)
+ end
+ end
+
+ context 'when domain has not project' do
+ let(:pages_domain) { create(:pages_domain) }
+
+ it 'returns false' do
+ is_expected.to eq(false)
+ end
+ end
+ end
+end
diff --git a/spec/migrations/backport_enterprise_schema_spec.rb b/spec/migrations/backport_enterprise_schema_spec.rb
new file mode 100644
index 00000000000..8d2d9d4953a
--- /dev/null
+++ b/spec/migrations/backport_enterprise_schema_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require Rails.root.join('db', 'migrate', '20190402150158_backport_enterprise_schema.rb')
+
+describe BackportEnterpriseSchema, :migration, schema: 20190329085614 do
+ include MigrationsHelpers
+
+ def drop_if_exists(table)
+ active_record_base.connection.drop_table(table) if active_record_base.connection.table_exists?(table)
+ end
+
+ describe '#up' do
+ it 'creates new EE tables' do
+ migrate!
+
+ expect(active_record_base.connection.table_exists?(:epics)).to be true
+ expect(active_record_base.connection.table_exists?(:geo_nodes)).to be true
+ end
+
+ context 'missing EE columns' do
+ before do
+ drop_if_exists(:epics)
+
+ active_record_base.connection.create_table "epics" do |t|
+ t.integer :group_id, null: false, index: true
+ t.integer :author_id, null: false, index: true
+ end
+ end
+
+ after do
+ drop_if_exists(:epics)
+ end
+
+ it 'flags an error' do
+ expect { migrate! }.to raise_error(/Your database is missing.*that is present for GitLab EE/)
+ end
+ end
+ end
+end
diff --git a/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb b/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb
index 2ae4872f51d..08a3511f70b 100644
--- a/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb
+++ b/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb
@@ -12,15 +12,18 @@ describe PagesDomainSslRenewalCronWorker do
end
describe '#perform' do
- let!(:domain) { create(:pages_domain) }
- let!(:domain_with_enabled_auto_ssl) { create(:pages_domain, auto_ssl_enabled: true) }
- let!(:domain_with_obtained_letsencrypt) { create(:pages_domain, :letsencrypt, auto_ssl_enabled: true) }
+ let(:project) { create :project }
+ let!(:domain) { create(:pages_domain, project: project) }
+ let!(:domain_with_enabled_auto_ssl) { create(:pages_domain, project: project, auto_ssl_enabled: true) }
+ let!(:domain_with_obtained_letsencrypt) do
+ create(:pages_domain, :letsencrypt, project: project, auto_ssl_enabled: true)
+ end
let!(:domain_without_auto_certificate) do
- create(:pages_domain, :without_certificate, :without_key, auto_ssl_enabled: true)
+ create(:pages_domain, :without_certificate, :without_key, project: project, auto_ssl_enabled: true)
end
let!(:domain_with_expired_auto_ssl) do
- create(:pages_domain, :letsencrypt, :with_expired_certificate)
+ create(:pages_domain, :letsencrypt, :with_expired_certificate, project: project)
end
it 'enqueues a PagesDomainSslRenewalWorker for domains needing renewal' do
diff --git a/spec/workers/pages_domain_ssl_renewal_worker_spec.rb b/spec/workers/pages_domain_ssl_renewal_worker_spec.rb
index a3d33de1b40..3552ff0823a 100644
--- a/spec/workers/pages_domain_ssl_renewal_worker_spec.rb
+++ b/spec/workers/pages_domain_ssl_renewal_worker_spec.rb
@@ -7,7 +7,8 @@ describe PagesDomainSslRenewalWorker do
subject(:worker) { described_class.new }
- let(:domain) { create(:pages_domain) }
+ let(:project) { create(:project) }
+ let(:domain) { create(:pages_domain, project: project) }
before do
stub_lets_encrypt_settings
@@ -22,14 +23,24 @@ describe PagesDomainSslRenewalWorker do
worker.perform(domain.id)
end
+ shared_examples 'does nothing' do
+ it 'does nothing' do
+ expect(::PagesDomains::ObtainLetsEncryptCertificateService).not_to receive(:new)
+ end
+ end
+
context 'when domain was deleted' do
before do
domain.destroy!
end
- it 'does nothing' do
- expect(::PagesDomains::ObtainLetsEncryptCertificateService).not_to receive(:new)
- end
+ include_examples 'does nothing'
+ end
+
+ context 'when domain is disabled' do
+ let(:domain) { create(:pages_domain, :disabled) }
+
+ include_examples 'does nothing'
end
end
end