Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-03-05 00:08:59 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-03-05 00:08:59 +0300
commit6609e5ea75a9e119651e19574c30c11ce19c62d0 (patch)
tree81a6e3b927ca6278983129e670a0ece1fce8c059
parent60bb1b9734536021c8eba9d15ac1a666af45be74 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/stage.vue2
-rw-r--r--app/assets/javascripts/profile/preferences/components/profile_preferences.vue26
-rw-r--r--app/assets/stylesheets/themes/theme_light.scss20
-rw-r--r--app/models/user.rb6
-rw-r--r--app/views/admin/deploy_keys/edit.html.haml4
-rw-r--r--app/views/admin/deploy_keys/index.html.haml2
-rw-r--r--app/views/admin/deploy_keys/new.html.haml4
-rw-r--r--changelogs/unreleased/273574-fix-bad-projects-has_external_issue_tracker-data.yml5
-rw-r--r--changelogs/unreleased/321862_fix_long_commit_messages_error.yml5
-rw-r--r--changelogs/unreleased/321929-fix-dark-mode-app-header-on-profile-preferences-page.yml5
-rw-r--r--changelogs/unreleased/323059-batch-load-findings-by-uuid.yml5
-rw-r--r--changelogs/unreleased/323175-database-timeout-in-pages-migration-task.yml5
-rw-r--r--changelogs/unreleased/alexpooley-remove_shared_group_membership_auth_ff.yml5
-rw-r--r--changelogs/unreleased/btn-confirm-admin-deploy-keys.yml5
-rw-r--r--config/feature_flags/development/shared_group_membership_auth.yml8
-rw-r--r--db/migrate/20210302103851_add_deployed_deployment_id_index_to_project_pages_metadata.rb19
-rw-r--r--db/post_migrate/20210210221006_cleanup_projects_with_bad_has_external_issue_tracker_data.rb84
-rw-r--r--db/schema_migrations/202102102210061
-rw-r--r--db/schema_migrations/202103021038511
-rw-r--r--db/structure.sql2
-rw-r--r--doc/administration/raketasks/check.md6
-rw-r--r--doc/development/fe_guide/vue.md8
-rw-r--r--doc/install/index.md105
-rw-r--r--doc/operations/incident_management/paging.md12
-rw-r--r--doc/topics/git/lfs/index.md43
-rw-r--r--doc/user/application_security/secret_detection/index.md5
-rw-r--r--lib/gitlab/tree_summary.rb10
-rw-r--r--scripts/review_apps/base-config.yaml20
-rw-r--r--spec/frontend/pipelines/stage_spec.js4
-rw-r--r--spec/frontend/profile/preferences/components/profile_preferences_spec.js147
-rw-r--r--spec/frontend/profile/preferences/mock_data.js12
-rw-r--r--spec/lib/gitlab/tree_summary_spec.rb31
-rw-r--r--spec/migrations/cleanup_projects_with_bad_has_external_issue_tracker_data_spec.rb94
-rw-r--r--spec/models/user_spec.rb19
34 files changed, 538 insertions, 192 deletions
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue b/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue
index dfa2c198158..1c02404870e 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue
@@ -41,7 +41,7 @@ export default {
isMergeTrain: {
type: Boolean,
required: false,
- default: true,
+ default: false,
},
},
data() {
diff --git a/app/assets/javascripts/profile/preferences/components/profile_preferences.vue b/app/assets/javascripts/profile/preferences/components/profile_preferences.vue
index 184ee3810ac..07d8f3cc5f1 100644
--- a/app/assets/javascripts/profile/preferences/components/profile_preferences.vue
+++ b/app/assets/javascripts/profile/preferences/components/profile_preferences.vue
@@ -44,6 +44,8 @@ export default {
data() {
return {
isSubmitEnabled: true,
+ darkModeOnCreate: null,
+ darkModeOnSubmit: null,
};
},
computed: {
@@ -58,6 +60,7 @@ export default {
this.formEl.addEventListener('ajax:beforeSend', this.handleLoading);
this.formEl.addEventListener('ajax:success', this.handleSuccess);
this.formEl.addEventListener('ajax:error', this.handleError);
+ this.darkModeOnCreate = this.darkModeSelected();
},
beforeDestroy() {
this.formEl.removeEventListener('ajax:beforeSend', this.handleLoading);
@@ -65,16 +68,27 @@ export default {
this.formEl.removeEventListener('ajax:error', this.handleError);
},
methods: {
+ darkModeSelected() {
+ const theme = this.getSelectedTheme();
+ return theme ? theme.css_class === 'gl-dark' : null;
+ },
+ getSelectedTheme() {
+ const themeId = new FormData(this.formEl).get('user[theme_id]');
+ return this.applicationThemes[themeId] ?? null;
+ },
handleLoading() {
this.isSubmitEnabled = false;
+ this.darkModeOnSubmit = this.darkModeSelected();
},
handleSuccess(customEvent) {
- const formData = new FormData(this.formEl);
- updateClasses(
- this.bodyClasses,
- this.applicationThemes[formData.get('user[theme_id]')].css_class,
- this.selectedLayout,
- );
+ // Reload the page if the theme has changed from light to dark mode or vice versa
+ // to correctly load all required styles.
+ const modeChanged = this.darkModeOnCreate ? !this.darkModeOnSubmit : this.darkModeOnSubmit;
+ if (modeChanged) {
+ window.location.reload();
+ return;
+ }
+ updateClasses(this.bodyClasses, this.getSelectedTheme().css_class, this.selectedLayout);
const { message = this.$options.i18n.defaultSuccess, type = FLASH_TYPES.NOTICE } =
customEvent?.detail?.[0] || {};
createFlash({ message, type });
diff --git a/app/assets/stylesheets/themes/theme_light.scss b/app/assets/stylesheets/themes/theme_light.scss
index 58003db4236..17192a061e1 100644
--- a/app/assets/stylesheets/themes/theme_light.scss
+++ b/app/assets/stylesheets/themes/theme_light.scss
@@ -84,11 +84,11 @@ body {
&.gl-dark {
.logo-text svg {
- fill: $gl-text-color;
+ fill: var(--gl-text-color);
}
.navbar-gitlab {
- background-color: $gray-50;
+ background-color: var(--gray-50);
.navbar-sub-nav,
.navbar-nav {
@@ -97,8 +97,8 @@ body {
> a:focus,
> button:hover,
> button:focus {
- color: $gl-text-color;
- background-color: $gray-200;
+ color: var(--gl-text-color);
+ background-color: var(--gray-200);
}
}
@@ -106,21 +106,21 @@ body {
li.dropdown.show {
> a,
> button {
- color: $gl-text-color;
- background-color: $gray-200;
+ color: var(--gl-text-color);
+ background-color: var(--gray-200);
}
}
}
.search {
form {
- background-color: $gray-100;
- box-shadow: inset 0 0 0 1px $border-color;
+ background-color: var(--gray-100);
+ box-shadow: inset 0 0 0 1px var(--border-color);
&:active,
&:hover {
- background-color: $gray-100;
- box-shadow: inset 0 0 0 1px $blue-200;
+ background-color: var(--gray-100);
+ box-shadow: inset 0 0 0 1px var(--blue-200);
}
}
}
diff --git a/app/models/user.rb b/app/models/user.rb
index dfaef473cc9..3f2d0bc9756 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -940,11 +940,7 @@ class User < ApplicationRecord
# Returns the groups a user has access to, either through a membership or a project authorization
def authorized_groups
Group.unscoped do
- if Feature.enabled?(:shared_group_membership_auth, self)
- authorized_groups_with_shared_membership
- else
- authorized_groups_without_shared_membership
- end
+ authorized_groups_with_shared_membership
end
end
diff --git a/app/views/admin/deploy_keys/edit.html.haml b/app/views/admin/deploy_keys/edit.html.haml
index 2a0177ab997..f85b37b3640 100644
--- a/app/views/admin/deploy_keys/edit.html.haml
+++ b/app/views/admin/deploy_keys/edit.html.haml
@@ -6,5 +6,5 @@
= form_for [:admin, @deploy_key], html: { class: 'deploy-key-form' } do |f|
= render partial: 'shared/deploy_keys/form', locals: { form: f, deploy_key: @deploy_key }
.form-actions
- = f.submit _('Save changes'), class: 'btn gl-button btn-success'
- = link_to _('Cancel'), admin_deploy_keys_path, class: 'btn gl-button btn-cancel'
+ = f.submit _('Save changes'), class: 'btn gl-button btn-confirm'
+ = link_to _('Cancel'), admin_deploy_keys_path, class: 'btn gl-button btn-default btn-cancel'
diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml
index 9b6aa278906..eec8f816f04 100644
--- a/app/views/admin/deploy_keys/index.html.haml
+++ b/app/views/admin/deploy_keys/index.html.haml
@@ -2,7 +2,7 @@
- if @deploy_keys.any?
%h3.page-title.deploy-keys-title
= _('Public deploy keys (%{deploy_keys_count})') % { deploy_keys_count: @deploy_keys.load.size }
- = link_to _('New deploy key'), new_admin_deploy_key_path, class: 'float-right btn gl-button btn-success btn-md gl-button'
+ = link_to _('New deploy key'), new_admin_deploy_key_path, class: 'float-right btn gl-button btn-confirm btn-md gl-button'
.table-holder.deploy-keys-list
%table.table
%thead
diff --git a/app/views/admin/deploy_keys/new.html.haml b/app/views/admin/deploy_keys/new.html.haml
index 5a3b880a596..dc49db6557b 100644
--- a/app/views/admin/deploy_keys/new.html.haml
+++ b/app/views/admin/deploy_keys/new.html.haml
@@ -6,5 +6,5 @@
= form_for [:admin, @deploy_key], html: { class: 'deploy-key-form' } do |f|
= render partial: 'shared/deploy_keys/form', locals: { form: f, deploy_key: @deploy_key }
.form-actions
- = f.submit 'Create', class: 'btn gl-button btn-success'
- = link_to 'Cancel', admin_deploy_keys_path, class: 'btn gl-button btn-cancel'
+ = f.submit 'Create', class: 'btn gl-button btn-confirm'
+ = link_to 'Cancel', admin_deploy_keys_path, class: 'btn gl-button btn-default btn-cancel'
diff --git a/changelogs/unreleased/273574-fix-bad-projects-has_external_issue_tracker-data.yml b/changelogs/unreleased/273574-fix-bad-projects-has_external_issue_tracker-data.yml
new file mode 100644
index 00000000000..4d8f19a04fa
--- /dev/null
+++ b/changelogs/unreleased/273574-fix-bad-projects-has_external_issue_tracker-data.yml
@@ -0,0 +1,5 @@
+---
+title: Cleanup incorrect data in projects.has_external_issue_tracker
+merge_request: 53936
+author:
+type: fixed
diff --git a/changelogs/unreleased/321862_fix_long_commit_messages_error.yml b/changelogs/unreleased/321862_fix_long_commit_messages_error.yml
new file mode 100644
index 00000000000..f21c864b7ed
--- /dev/null
+++ b/changelogs/unreleased/321862_fix_long_commit_messages_error.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 500 error for long commit messages
+merge_request: 55320
+author:
+type: fixed
diff --git a/changelogs/unreleased/321929-fix-dark-mode-app-header-on-profile-preferences-page.yml b/changelogs/unreleased/321929-fix-dark-mode-app-header-on-profile-preferences-page.yml
new file mode 100644
index 00000000000..9d2647b3904
--- /dev/null
+++ b/changelogs/unreleased/321929-fix-dark-mode-app-header-on-profile-preferences-page.yml
@@ -0,0 +1,5 @@
+---
+title: Correctly style Dark Mode application header in profile preferences
+merge_request: 54575
+author: Simon Stieger @sim0
+type: fixed
diff --git a/changelogs/unreleased/323059-batch-load-findings-by-uuid.yml b/changelogs/unreleased/323059-batch-load-findings-by-uuid.yml
new file mode 100644
index 00000000000..b2b20c337b8
--- /dev/null
+++ b/changelogs/unreleased/323059-batch-load-findings-by-uuid.yml
@@ -0,0 +1,5 @@
+---
+title: Batch-load vulnerability findings by UUID
+merge_request: 55642
+author:
+type: performance
diff --git a/changelogs/unreleased/323175-database-timeout-in-pages-migration-task.yml b/changelogs/unreleased/323175-database-timeout-in-pages-migration-task.yml
new file mode 100644
index 00000000000..9d7fec257f5
--- /dev/null
+++ b/changelogs/unreleased/323175-database-timeout-in-pages-migration-task.yml
@@ -0,0 +1,5 @@
+---
+title: Add index for pages migration
+merge_request: 55757
+author:
+type: added
diff --git a/changelogs/unreleased/alexpooley-remove_shared_group_membership_auth_ff.yml b/changelogs/unreleased/alexpooley-remove_shared_group_membership_auth_ff.yml
new file mode 100644
index 00000000000..2d676854b3f
--- /dev/null
+++ b/changelogs/unreleased/alexpooley-remove_shared_group_membership_auth_ff.yml
@@ -0,0 +1,5 @@
+---
+title: Include shared with groups in list of authorized groups
+merge_request: 54894
+author:
+type: fixed
diff --git a/changelogs/unreleased/btn-confirm-admin-deploy-keys.yml b/changelogs/unreleased/btn-confirm-admin-deploy-keys.yml
new file mode 100644
index 00000000000..108545d6b10
--- /dev/null
+++ b/changelogs/unreleased/btn-confirm-admin-deploy-keys.yml
@@ -0,0 +1,5 @@
+---
+title: Move to btn-confirm from btn-success in admin/deploy_keys directory
+merge_request: 55267
+author: Yogi (@yo)
+type: changed
diff --git a/config/feature_flags/development/shared_group_membership_auth.yml b/config/feature_flags/development/shared_group_membership_auth.yml
deleted file mode 100644
index e6aaad9bbd6..00000000000
--- a/config/feature_flags/development/shared_group_membership_auth.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: shared_group_membership_auth
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46412
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/224771
-milestone: '13.6'
-type: development
-group: group::access
-default_enabled: false
diff --git a/db/migrate/20210302103851_add_deployed_deployment_id_index_to_project_pages_metadata.rb b/db/migrate/20210302103851_add_deployed_deployment_id_index_to_project_pages_metadata.rb
new file mode 100644
index 00000000000..e10e9a912cc
--- /dev/null
+++ b/db/migrate/20210302103851_add_deployed_deployment_id_index_to_project_pages_metadata.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddDeployedDeploymentIdIndexToProjectPagesMetadata < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ INDEX_NAME = 'index_on_pages_metadata_not_migrated'
+
+ def up
+ add_concurrent_index :project_pages_metadata, :project_id, where: "deployed = TRUE AND pages_deployment_id is NULL", name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name :project_pages_metadata, INDEX_NAME
+ end
+end
diff --git a/db/post_migrate/20210210221006_cleanup_projects_with_bad_has_external_issue_tracker_data.rb b/db/post_migrate/20210210221006_cleanup_projects_with_bad_has_external_issue_tracker_data.rb
new file mode 100644
index 00000000000..4b8bf014066
--- /dev/null
+++ b/db/post_migrate/20210210221006_cleanup_projects_with_bad_has_external_issue_tracker_data.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+class CleanupProjectsWithBadHasExternalIssueTrackerData < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ TMP_INDEX_NAME = 'tmp_idx_projects_on_id_where_has_external_issue_tracker_is_true'.freeze
+ BATCH_SIZE = 100
+
+ disable_ddl_transaction!
+
+ class Service < ActiveRecord::Base
+ include EachBatch
+ belongs_to :project
+
+ self.table_name = 'services'
+ self.inheritance_column = :_type_disabled
+ end
+
+ class Project < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'projects'
+ end
+
+ def up
+ update_projects_with_active_external_issue_trackers
+ update_projects_without_active_external_issue_trackers
+ end
+
+ def down
+ # no-op : can't go back to incorrect data
+ end
+
+ private
+
+ def update_projects_with_active_external_issue_trackers
+ scope = Service.where(active: true, category: 'issue_tracker').where.not(project_id: nil).distinct(:project_id)
+
+ scope.each_batch(of: BATCH_SIZE) do |relation|
+ scope_with_projects = relation
+ .joins(:project)
+ .select('project_id')
+ .merge(Project.where(has_external_issue_tracker: false).where(pending_delete: false))
+
+ execute(<<~SQL)
+ WITH project_ids_to_update (id) AS (
+ #{scope_with_projects.to_sql}
+ )
+ UPDATE projects SET has_external_issue_tracker = true WHERE id IN (SELECT id FROM project_ids_to_update)
+ SQL
+ end
+ end
+
+ def update_projects_without_active_external_issue_trackers
+ # Add a temporary index to speed up the scoping of projects.
+ index_where = <<~SQL
+ "projects"."has_external_issue_tracker" = TRUE
+ AND "projects"."pending_delete" = FALSE
+ SQL
+
+ add_concurrent_index(:projects, :id, where: index_where, name: TMP_INDEX_NAME)
+
+ services_sub_query = Service
+ .select('1')
+ .where('services.project_id = projects.id')
+ .where(category: 'issue_tracker')
+ .where(active: true)
+
+ # 322 projects are scoped in this query on GitLab.com.
+ Project.where(index_where).each_batch(of: BATCH_SIZE) do |relation|
+ relation_with_exists_query = relation.where('NOT EXISTS (?)', services_sub_query)
+ execute(<<~SQL)
+ WITH project_ids_to_update (id) AS (
+ #{relation_with_exists_query.select(:id).to_sql}
+ )
+ UPDATE projects SET has_external_issue_tracker = false WHERE id IN (SELECT id FROM project_ids_to_update)
+ SQL
+ end
+
+ # Drop the temporary index.
+ remove_concurrent_index_by_name(:projects, TMP_INDEX_NAME)
+ end
+end
diff --git a/db/schema_migrations/20210210221006 b/db/schema_migrations/20210210221006
new file mode 100644
index 00000000000..5292b5c0dce
--- /dev/null
+++ b/db/schema_migrations/20210210221006
@@ -0,0 +1 @@
+1ff1256d2deac0a1545ef7db30d8ba7969265d6c2df62f6bd20f9f1721a482cb \ No newline at end of file
diff --git a/db/schema_migrations/20210302103851 b/db/schema_migrations/20210302103851
new file mode 100644
index 00000000000..cd166f974a9
--- /dev/null
+++ b/db/schema_migrations/20210302103851
@@ -0,0 +1 @@
+b2dad27276941e17248f86764196525bd91b088eed78ad7aa6ae2e5a2c9e82bd \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 2ee32f716c5..2d9a97c9817 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -22909,6 +22909,8 @@ CREATE INDEX index_on_namespaces_lower_name ON namespaces USING btree (lower((na
CREATE INDEX index_on_namespaces_lower_path ON namespaces USING btree (lower((path)::text));
+CREATE INDEX index_on_pages_metadata_not_migrated ON project_pages_metadata USING btree (project_id) WHERE ((deployed = true) AND (pages_deployment_id IS NULL));
+
CREATE INDEX index_on_projects_lower_path ON projects USING btree (lower((path)::text));
CREATE INDEX index_on_routes_lower_path ON routes USING btree (lower((path)::text));
diff --git a/doc/administration/raketasks/check.md b/doc/administration/raketasks/check.md
index 5ec268ac769..8d2ca103c82 100644
--- a/doc/administration/raketasks/check.md
+++ b/doc/administration/raketasks/check.md
@@ -245,3 +245,9 @@ Upload.find_each do |upload|
end
p "#{uploads_deleted} remote objects were destroyed."
```
+
+### Delete references to missing LFS objects
+
+If `gitlab-rake gitlab:lfs:check VERBOSE=1` detects LFS objects that exist in the database
+but not on disk, [follow the procedure in the LFS documentation](../../topics/git/lfs/index.md#missing-lfs-objects)
+to remove the database entries.
diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md
index 5b902e1b16e..220a4a107aa 100644
--- a/doc/development/fe_guide/vue.md
+++ b/doc/development/fe_guide/vue.md
@@ -237,6 +237,9 @@ Each Vue component has a unique output. This output is always present in the ren
Although each method of a Vue component can be tested individually, our goal is to test the output
of the render function, which represents the state at all times.
+Visit the [Vue testing guide](https://vuejs.org/v2/guide/testing.html#Unit-Testing) for help
+testing the rendered output.
+
Here's an example of a well structured unit test for [this Vue component](#appendix---vue-component-subject-under-test):
```javascript
@@ -331,11 +334,6 @@ describe('~/todos/app.vue', () => {
});
```
-### Test the component's output
-
-The main return value of a Vue component is the rendered output. In order to test the component we
-need to test the rendered output. Visit the [Vue testing guide](https://vuejs.org/v2/guide/testing.html#Unit-Testing).
-
### Child components
1. Test any directive that defines if/how child component is rendered (for example, `v-if` and `v-for`).
diff --git a/doc/install/index.md b/doc/install/index.md
index d7c562ac77b..1e6f0bb95c2 100644
--- a/doc/install/index.md
+++ b/doc/install/index.md
@@ -9,110 +9,49 @@ type: index
# Installation **(FREE SELF)**
-GitLab can be installed in most GNU/Linux distributions and with several
+GitLab can be installed in most GNU/Linux distributions, and with several
cloud providers. To get the best experience from GitLab, you must balance
performance, reliability, ease of administration (backups, upgrades, and
troubleshooting), and the cost of hosting.
-Depending on your platform, select from the following available methods to
-install GitLab:
-
-- [_Omnibus GitLab_](#installing-gitlab-on-linux-using-the-omnibus-gitlab-package-recommended):
- The official deb/rpm packages that contain a bundle of GitLab and the
- components it depends on, including PostgreSQL, Redis, and Sidekiq.
-- [_GitLab Helm chart_](#installing-gitlab-on-kubernetes-via-the-gitlab-helm-charts):
- The cloud native Helm chart for installing GitLab and all of its components
- on Kubernetes.
-- [_Docker_](#installing-gitlab-with-docker): The Omnibus GitLab packages,
- Dockerized.
-- [_Source_](#installing-gitlab-from-source): Install GitLab and all of its
- components from scratch.
-- [_Cloud provider_](#installing-gitlab-on-cloud-providers): Install directly
- from platforms like AWS, Azure, and GCP.
-
-If you're not sure which installation method to use, we recommend you use
-Omnibus GitLab. The Omnibus GitLab packages are mature,
-[scalable](../administration/reference_architectures/index.md), and are used
-today on GitLab.com. The Helm charts are recommended for those who are familiar
-with Kubernetes.
-
## Requirements
Before you install GitLab, be sure to review the [system requirements](requirements.md).
The system requirements include details about the minimum hardware, software,
database, and additional requirements to support GitLab.
-## Installing GitLab on Linux using the Omnibus GitLab package (recommended)
-
-The Omnibus GitLab package uses our official deb/rpm repositories, and is
-recommended for most users.
-
-If you need additional scale or resilience, we recommend deploying
-GitLab as described in our [reference architecture documentation](../administration/reference_architectures/index.md).
-
-[**> Install GitLab using the Omnibus GitLab package.**](https://about.gitlab.com/install/)
-
-### GitLab Environment Toolkit (GET)
-
-The [GitLab Environment Toolkit](https://gitlab.com/gitlab-org/quality/gitlab-environment-toolkit) (GET) provides a set of automation tools to easily deploy a [reference architecture](../administration/reference_architectures/index.md) on most major cloud providers.
-
-It is currently in beta, and is not yet recommended for production use.
-
-[**> Install a GitLab reference architecture using the GitLab Environment Toolkit.**](https://gitlab.com/gitlab-org/quality/gitlab-environment-toolkit#documentation)
+## Choose the installation method
-## Installing GitLab on Kubernetes via the GitLab Helm charts
-
-When installing GitLab on Kubernetes, there are some trade-offs that you
-need to be aware of:
-
-- Administration and troubleshooting requires Kubernetes knowledge.
-- It can be more expensive for smaller installations. The default installation
- requires more resources than a single node Omnibus deployment, as most services
- are deployed in a redundant fashion.
-- There are some feature [limitations to be aware of](https://docs.gitlab.com/charts/#limitations).
-
-Due to these trade-offs, having Kubernetes experience is a requirement for
-using this method. We recommend being familiar with Kubernetes before using it
-to deploy GitLab in production. The methods for management, observability, and
-some concepts are different than traditional deployments.
-
-[**> Install GitLab on Kubernetes using the GitLab Helm charts.**](https://docs.gitlab.com/charts/)
-
-## Installing GitLab with Docker
-
-GitLab maintains a set of official Docker images based on the Omnibus GitLab
-package.
-
-[**> Install GitLab using the official GitLab Docker images.**](docker.md)
-
-## Installing GitLab from source
-
-If the Omnibus GitLab package isn't available for your distribution, you can
-install GitLab from source. This can be useful with unsupported systems, like
-\*BSD. For an overview of the directory structure, see the
-[structure documentation](installation.md#gitlab-directory-structure).
+Depending on your platform, select from the following available methods to
+install GitLab:
-[**> Install GitLab from source.**](installation.md)
+| Installation method | Description | When to choose |
+|----------------------------------------------------------------|-------------|----------------|
+| [Linux package](https://docs.gitlab.com/omnibus/installation/) | The official deb/rpm packages (also known as Omnibus GitLab) that contains a bundle of GitLab and the components it depends on, including PostgreSQL, Redis, and Sidekiq. | This is the recommended method for getting started. The Linux packages are mature, scalable, and are used today on GitLab.com. If you need additional flexibility and resilience, we recommend deploying GitLab as described in the [reference architecture documentation](../administration/reference_architectures/index.md). |
+| [Helm charts](https://docs.gitlab.com/charts/) | The cloud native Helm chart for installing GitLab and all of its components on Kubernetes. | When installing GitLab on Kubernetes, there are some trade-offs that you need to be aware of: <br/>- Administration and troubleshooting requires Kubernetes knowledge.<br/>- It can be more expensive for smaller installations. The default installation requires more resources than a single node Linux package deployment, as most services are deployed in a redundant fashion.<br/>- There are some feature [limitations to be aware of](https://docs.gitlab.com/charts/#limitations).<br/><br/> Use this method if your infrastructure is built on Kubernetes and you're familiar with how it works. The methods for management, observability, and some concepts are different than traditional deployments. |
+| [Docker](https://docs.gitlab.com/omnibus/docker/) | The GitLab packages, Dockerized. | Use this method if you're familiar with Docker. |
+| [Source](installation.md) | Install GitLab and all of its components from scratch. | Use this method if none of the previous methods are available for your platform. Useful for unsupported systems like \*BSD.|
+| [GitLab Environment Toolkit (GET)](https://gitlab.com/gitlab-org/quality/gitlab-environment-toolkit#documentation) | The GitLab Environment toolkit provides a set of automation tools to deploy a [reference architecture](../administration/reference_architectures/index.md) on most major cloud providers. | Since GET is in beta and not yet recommended for production use, use this method if you want to test deploying GitLab in scalable environment. |
-## Installing GitLab on cloud providers
+## Install GitLab on cloud providers
-GitLab can be installed on a variety of cloud providers by using any of
-the above methods, provided the cloud provider supports it.
+Regardless of the installation method, you can install GitLab on several cloud
+providers, assuming the cloud provider supports it. Here are several possible installation
+methods, the majority which use the Linux packages:
-- [Install on AWS](aws/index.md): Install Omnibus GitLab on AWS using the community AMIs that GitLab provides.
-- [Install GitLab on Google Cloud Platform](google_cloud_platform/index.md): Install Omnibus GitLab on a VM in GCP.
-- [Install GitLab on Azure](azure/index.md): Install Omnibus GitLab from Azure Marketplace.
-- [Install GitLab on OpenShift](https://docs.gitlab.com/charts/installation/cloud/openshift.html): Install GitLab on OpenShift by using the GitLab Helm charts.
-- [Install GitLab on DigitalOcean](https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-gitlab-on-ubuntu-18-04): Install Omnibus GitLab on DigitalOcean.
-- _Testing only!_ [DigitalOcean and Docker Machine](digitaloceandocker.md):
- Quickly test any version of GitLab on DigitalOcean using Docker Machine.
+| Cloud provider | Description |
+|---------------------------------------------------------------|-------------|
+| [AWS (HA)](aws/index.md) | Install GitLab on AWS using the community AMIs provided by GitLab. |
+| [Google Cloud Platform (GCP)](google_cloud_platform/index.md) | Install GitLab on a VM in GCP. |
+| [Azure](azure/index.md) | Install GitLab from Azure Marketplace. |
+| [DigitalOcean](https://about.gitlab.com/blog/2016/04/27/getting-started-with-gitlab-and-digitalocean/) | Install GitLab on DigitalOcean. You can also [test GitLab on DigitalOcean using Docker Machine](digitaloceandocker.md). |
## Next steps
Here are a few resources you might want to check out after completing the
installation:
-- [Upload a license](../user/admin_area/license.md) or [start a free trial](https://about.gitlab.com/free-trial/):
+- [Upload a license](../user/admin_area/license.md) or [start a free trial](https://about.gitlab.com/free-trial/):
Activate all GitLab Enterprise Edition functionality with a license.
- [Set up runners](https://docs.gitlab.com/runner/): Set up one or more GitLab
Runners, the agents that are responsible for all of the GitLab CI/CD features.
diff --git a/doc/operations/incident_management/paging.md b/doc/operations/incident_management/paging.md
index dd04cd63a54..4801f94d494 100644
--- a/doc/operations/incident_management/paging.md
+++ b/doc/operations/incident_management/paging.md
@@ -23,14 +23,12 @@ you never miss a page.
## Email notifications
-Email notifications are available in projects that have been
-[configured to create incidents automatically](incidents.md#create-incidents-automatically)
-for triggered alerts. Project members with the **Owner** or **Maintainer** roles are
-sent an email notification automatically. (This is not configurable.) To optionally
-send additional email notifications to project members with the **Developer** role:
+Email notifications are available in projects for triggered alerts. Project
+members with the **Owner** or **Maintainer** roles have the option to receive
+a single email notification for new alerts.
1. Navigate to **Settings > Operations**.
1. Expand the **Incidents** section.
-1. In the **Alert Integration** tab, select the **Send a separate email notification to Developers**
- check box.
+1. In the **Alert Integration** tab, select the checkbox
+ **Send a single email notification to Owners and Maintainers for new alerts**.
1. Select **Save changes**.
diff --git a/doc/topics/git/lfs/index.md b/doc/topics/git/lfs/index.md
index 14bb28d2477..11c0fcc2373 100644
--- a/doc/topics/git/lfs/index.md
+++ b/doc/topics/git/lfs/index.md
@@ -269,3 +269,46 @@ You might choose to do this if you are using an appliance like a <!-- vale gitla
GitLab can't verify LFS objects. Pushes then fail if you have GitLab LFS support enabled.
To stop push failure, LFS support can be disabled in the [Project settings](../../../user/project/settings/index.md), which also disables GitLab LFS value-adds (Verifying LFS objects, UI integration for LFS).
+
+### Missing LFS objects
+
+An error about a missing LFS object may occur in either of these situations:
+
+- When migrating LFS objects from disk to object storage, with error messages like:
+
+ ```plaintext
+ ERROR -- : Failed to transfer LFS object
+ 006622269c61b41bf14a22bbe0e43be3acf86a4a446afb4250c3794ea47541a7
+ with error: No such file or directory @ rb_sysopen -
+ /var/opt/gitlab/gitlab-rails/shared/lfs-objects/00/66/22269c61b41bf14a22bbe0e43be3acf86a4a446afb4250c3794ea47541a7
+ ```
+
+ (Line breaks have been added for legibility.)
+
+- When running the
+ [integrity check for LFS objects](../../../administration/raketasks/check.md#uploaded-files-integrity)
+ with the `VERBOSE=1` parameter.
+
+The database can have records for LFS objects which are not on disk. The database entry may
+[prevent a new copy of the object being pushed](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/49241).
+To delete these references:
+
+1. [Start a rails console](../../../administration/operations/rails_console.md).
+1. Query the object that's reported as missing in the rails console, to return a file path:
+
+ ```ruby
+ lfs_object = LfsObject.find_by(oid: '006622269c61b41bf14a22bbe0e43be3acf86a4a446afb4250c3794ea47541a7')
+ lfs_object.file.path
+ ```
+
+1. Check on disk if it exists:
+
+ ```shell
+ ls -al /var/opt/gitlab/gitlab-rails/shared/lfs-objects/00/66/22269c61b41bf14a22bbe0e43be3acf86a4a446afb4250c3794ea47541a7
+ ```
+
+1. If the file is not present, remove the database record via the rails console:
+
+ ```ruby
+ lfs_object.destroy
+ ```
diff --git a/doc/user/application_security/secret_detection/index.md b/doc/user/application_security/secret_detection/index.md
index 9390a5def18..d3709023085 100644
--- a/doc/user/application_security/secret_detection/index.md
+++ b/doc/user/application_security/secret_detection/index.md
@@ -102,8 +102,7 @@ as shown in the following table:
Secret Detection is performed by a [specific analyzer](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/Secret-Detection.gitlab-ci.yml)
during the `secret-detection` job. It runs regardless of your app's programming language.
-The Secret Detection analyzer includes [Gitleaks](https://github.com/zricethezav/gitleaks) and
-[TruffleHog](https://github.com/dxa4481/truffleHog) checks.
+The Secret Detection analyzer includes [Gitleaks](https://github.com/zricethezav/gitleaks) checks.
Note that the Secret Detection analyzer ignores Password-in-URL vulnerabilities if the password
begins with a dollar sign (`$`), as this likely indicates the password is an environment variable.
@@ -200,7 +199,7 @@ Secret Detection can be customized by defining available CI/CD variables:
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/211387) in GitLab 13.5.
You can customize the default secret detection rules provided with GitLab.
-Customization allows you to exclude rules and add new rules.
+Customization allows replace the default secret detection rules with rules that you define.
To create a custom ruleset:
diff --git a/lib/gitlab/tree_summary.rb b/lib/gitlab/tree_summary.rb
index bc7b8bd2b94..86cd91f0a32 100644
--- a/lib/gitlab/tree_summary.rb
+++ b/lib/gitlab/tree_summary.rb
@@ -104,12 +104,12 @@ module Gitlab
end
def fetch_last_cached_commits_list
- cache_key = ['projects', project.id, 'last_commits_list', commit.id, ensured_path, offset, limit]
+ cache_key = ['projects', project.id, 'last_commits', commit.id, ensured_path, offset, limit]
commits = Rails.cache.fetch(cache_key, expires_in: CACHE_EXPIRE_IN) do
repository
.list_last_commits_for_tree(commit.id, ensured_path, offset: offset, limit: limit, literal_pathspec: true)
- .transform_values!(&:to_hash)
+ .transform_values! { |commit| commit_to_hash(commit) }
end
commits.transform_values! { |value| Commit.from_hash(value, project) }
@@ -121,6 +121,12 @@ module Gitlab
resolved_commits[commit.id] ||= commit
end
+ def commit_to_hash(commit)
+ commit.to_hash.tap do |hash|
+ hash[:message] = hash[:message].to_s.truncate_bytes(1.kilobyte, omission: '...')
+ end
+ end
+
def commit_path(commit)
Gitlab::Routing.url_helpers.project_commit_path(project, commit)
end
diff --git a/scripts/review_apps/base-config.yaml b/scripts/review_apps/base-config.yaml
index bfc35a6abde..cf55fca7452 100644
--- a/scripts/review_apps/base-config.yaml
+++ b/scripts/review_apps/base-config.yaml
@@ -16,11 +16,11 @@ gitlab:
gitaly:
resources:
requests:
- cpu: 1200m
- memory: 245M
+ cpu: 2400m
+ memory: 1000M
limits:
- cpu: 1800m
- memory: 367M
+ cpu: 3600m
+ memory: 1500M
persistence:
size: 10G
gitlab-exporter:
@@ -38,10 +38,10 @@ gitlab:
resources:
requests:
cpu: 500m
- memory: 25M
+ memory: 100M
limits:
cpu: 750m
- memory: 37.5M
+ memory: 150M
maxReplicas: 3
hpa:
targetAverageValue: 500m
@@ -52,10 +52,10 @@ gitlab:
resources:
requests:
cpu: 855m
- memory: 1285M
+ memory: 1927M
limits:
cpu: 1282m
- memory: 1927M
+ memory: 2890M
hpa:
targetAverageValue: 650m
task-runner:
@@ -138,10 +138,10 @@ postgresql:
resources:
requests:
cpu: 550m
- memory: 250M
+ memory: 1000M
limits:
cpu: 825m
- memory: 375M
+ memory: 1500M
prometheus:
install: false
redis:
diff --git a/spec/frontend/pipelines/stage_spec.js b/spec/frontend/pipelines/stage_spec.js
index 7820e26e2c2..832c8fa8aa4 100644
--- a/spec/frontend/pipelines/stage_spec.js
+++ b/spec/frontend/pipelines/stage_spec.js
@@ -195,9 +195,7 @@ describe('Pipelines stage component', () => {
describe('With merge trains disabled', () => {
beforeEach(async () => {
mock.onGet(dropdownPath).reply(200, stageReply);
- createComponent({
- isMergeTrain: false,
- });
+ createComponent();
await openStageDropdown();
await axios.waitForAll();
diff --git a/spec/frontend/profile/preferences/components/profile_preferences_spec.js b/spec/frontend/profile/preferences/components/profile_preferences_spec.js
index 82c41178410..9e6f5594d26 100644
--- a/spec/frontend/profile/preferences/components/profile_preferences_spec.js
+++ b/spec/frontend/profile/preferences/components/profile_preferences_spec.js
@@ -1,10 +1,19 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import IntegrationView from '~/profile/preferences/components/integration_view.vue';
import ProfilePreferences from '~/profile/preferences/components/profile_preferences.vue';
import { i18n } from '~/profile/preferences/constants';
-import { integrationViews, userFields, bodyClasses } from '../mock_data';
+import {
+ integrationViews,
+ userFields,
+ bodyClasses,
+ themes,
+ lightModeThemeId1,
+ darkModeThemeId,
+ lightModeThemeId2,
+} from '../mock_data';
const expectedUrl = '/foo';
@@ -14,7 +23,7 @@ describe('ProfilePreferences component', () => {
integrationViews: [],
userFields,
bodyClasses,
- themes: [{ id: 1, css_class: 'foo' }],
+ themes,
profilePreferencesPath: '/update-profile',
formEl: document.createElement('form'),
};
@@ -49,6 +58,30 @@ describe('ProfilePreferences component', () => {
return document.querySelector('.flash-container .flash-text');
}
+ function createThemeInput(themeId = lightModeThemeId1) {
+ const input = document.createElement('input');
+ input.setAttribute('name', 'user[theme_id]');
+ input.setAttribute('type', 'radio');
+ input.setAttribute('value', themeId.toString());
+ input.setAttribute('checked', 'checked');
+ return input;
+ }
+
+ function createForm(themeInput = createThemeInput()) {
+ const form = document.createElement('form');
+ form.setAttribute('url', expectedUrl);
+ form.setAttribute('method', 'put');
+ form.appendChild(themeInput);
+ return form;
+ }
+
+ function setupBody() {
+ const div = document.createElement('div');
+ div.classList.add('container-fluid');
+ document.body.appendChild(div);
+ document.body.classList.add('content-wrapper');
+ }
+
beforeEach(() => {
setFixtures('<div class="flash-container"></div>');
});
@@ -84,30 +117,15 @@ describe('ProfilePreferences component', () => {
let form;
beforeEach(() => {
- const div = document.createElement('div');
- div.classList.add('container-fluid');
- document.body.appendChild(div);
- document.body.classList.add('content-wrapper');
-
- form = document.createElement('form');
- form.setAttribute('url', expectedUrl);
- form.setAttribute('method', 'put');
-
- const input = document.createElement('input');
- input.setAttribute('name', 'user[theme_id]');
- input.setAttribute('type', 'radio');
- input.setAttribute('value', '1');
- input.setAttribute('checked', 'checked');
- form.appendChild(input);
-
+ setupBody();
+ form = createForm();
wrapper = createComponent({ provide: { formEl: form }, attachTo: document.body });
-
const beforeSendEvent = new CustomEvent('ajax:beforeSend');
form.dispatchEvent(beforeSendEvent);
});
it('disables the submit button', async () => {
- await wrapper.vm.$nextTick();
+ await nextTick();
const button = findSubmitButton();
expect(button.props('disabled')).toBe(true);
});
@@ -116,7 +134,7 @@ describe('ProfilePreferences component', () => {
const successEvent = new CustomEvent('ajax:success');
form.dispatchEvent(successEvent);
- await wrapper.vm.$nextTick();
+ await nextTick();
const button = findSubmitButton();
expect(button.props('disabled')).toBe(false);
});
@@ -125,7 +143,7 @@ describe('ProfilePreferences component', () => {
const errorEvent = new CustomEvent('ajax:error');
form.dispatchEvent(errorEvent);
- await wrapper.vm.$nextTick();
+ await nextTick();
const button = findSubmitButton();
expect(button.props('disabled')).toBe(false);
});
@@ -160,4 +178,89 @@ describe('ProfilePreferences component', () => {
expect(findFlashError().innerText.trim()).toEqual(message);
});
});
+
+ describe('theme changes', () => {
+ const { location } = window;
+
+ let themeInput;
+ let form;
+
+ function setupWrapper() {
+ wrapper = createComponent({ provide: { formEl: form }, attachTo: document.body });
+ }
+
+ function selectThemeId(themeId) {
+ themeInput.setAttribute('value', themeId.toString());
+ }
+
+ function dispatchBeforeSendEvent() {
+ const beforeSendEvent = new CustomEvent('ajax:beforeSend');
+ form.dispatchEvent(beforeSendEvent);
+ }
+
+ function dispatchSuccessEvent() {
+ const successEvent = new CustomEvent('ajax:success');
+ form.dispatchEvent(successEvent);
+ }
+
+ beforeAll(() => {
+ delete window.location;
+ window.location = {
+ ...location,
+ reload: jest.fn(),
+ };
+ });
+
+ afterAll(() => {
+ window.location = location;
+ });
+
+ beforeEach(() => {
+ setupBody();
+ themeInput = createThemeInput();
+ form = createForm(themeInput);
+ });
+
+ it('reloads the page when switching from light to dark mode', async () => {
+ selectThemeId(lightModeThemeId1);
+ setupWrapper();
+
+ selectThemeId(darkModeThemeId);
+ dispatchBeforeSendEvent();
+ await nextTick();
+
+ dispatchSuccessEvent();
+ await nextTick();
+
+ expect(window.location.reload).toHaveBeenCalledTimes(1);
+ });
+
+ it('reloads the page when switching from dark to light mode', async () => {
+ selectThemeId(darkModeThemeId);
+ setupWrapper();
+
+ selectThemeId(lightModeThemeId1);
+ dispatchBeforeSendEvent();
+ await nextTick();
+
+ dispatchSuccessEvent();
+ await nextTick();
+
+ expect(window.location.reload).toHaveBeenCalledTimes(1);
+ });
+
+ it('does not reload the page when switching between light mode themes', async () => {
+ selectThemeId(lightModeThemeId1);
+ setupWrapper();
+
+ selectThemeId(lightModeThemeId2);
+ dispatchBeforeSendEvent();
+ await nextTick();
+
+ dispatchSuccessEvent();
+ await nextTick();
+
+ expect(window.location.reload).not.toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/frontend/profile/preferences/mock_data.js b/spec/frontend/profile/preferences/mock_data.js
index ce33fc79a39..91cfdfadc78 100644
--- a/spec/frontend/profile/preferences/mock_data.js
+++ b/spec/frontend/profile/preferences/mock_data.js
@@ -18,3 +18,15 @@ export const userFields = {
};
export const bodyClasses = 'ui-light-indigo ui-light gl-dark';
+
+export const themes = [
+ { id: 1, css_class: 'foo' },
+ { id: 2, css_class: 'bar' },
+ { id: 3, css_class: 'gl-dark' },
+];
+
+export const lightModeThemeId1 = 1;
+
+export const lightModeThemeId2 = 2;
+
+export const darkModeThemeId = 3;
diff --git a/spec/lib/gitlab/tree_summary_spec.rb b/spec/lib/gitlab/tree_summary_spec.rb
index d2c5844b0fa..661ef507a82 100644
--- a/spec/lib/gitlab/tree_summary_spec.rb
+++ b/spec/lib/gitlab/tree_summary_spec.rb
@@ -57,14 +57,12 @@ RSpec.describe Gitlab::TreeSummary do
context 'with caching', :use_clean_rails_memory_store_caching do
subject { Rails.cache.fetch(key) }
- before do
- summarized
- end
-
context 'Repository tree cache' do
let(:key) { ['projects', project.id, 'content', commit.id, path] }
it 'creates a cache for repository content' do
+ summarized
+
is_expected.to eq([{ file_name: 'a.txt', type: :blob }])
end
end
@@ -72,11 +70,34 @@ RSpec.describe Gitlab::TreeSummary do
context 'Commits list cache' do
let(:offset) { 0 }
let(:limit) { 25 }
- let(:key) { ['projects', project.id, 'last_commits_list', commit.id, path, offset, limit] }
+ let(:key) { ['projects', project.id, 'last_commits', commit.id, path, offset, limit] }
it 'creates a cache for commits list' do
+ summarized
+
is_expected.to eq('a.txt' => commit.to_hash)
end
+
+ context 'when commit has a very long message' do
+ before do
+ repo.create_file(
+ project.creator,
+ 'long.txt',
+ '',
+ message: message,
+ branch_name: project.default_branch_or_master
+ )
+ end
+
+ let(:message) { 'a' * 1025 }
+ let(:expected_message) { message[0...1021] + '...' }
+
+ it 'truncates commit message to 1 kilobyte' do
+ summarized
+
+ is_expected.to include('long.txt' => a_hash_including(message: expected_message))
+ end
+ end
end
end
end
diff --git a/spec/migrations/cleanup_projects_with_bad_has_external_issue_tracker_data_spec.rb b/spec/migrations/cleanup_projects_with_bad_has_external_issue_tracker_data_spec.rb
new file mode 100644
index 00000000000..8aedd1f9607
--- /dev/null
+++ b/spec/migrations/cleanup_projects_with_bad_has_external_issue_tracker_data_spec.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_migration!
+
+RSpec.describe CleanupProjectsWithBadHasExternalIssueTrackerData, :migration do
+ let(:namespace) { table(:namespaces).create!(name: 'foo', path: 'bar') }
+ let(:projects) { table(:projects) }
+ let(:services) { table(:services) }
+
+ def create_projects!(num)
+ Array.new(num) do
+ projects.create!(namespace_id: namespace.id)
+ end
+ end
+
+ def create_active_external_issue_tracker_integrations!(*projects)
+ projects.each do |project|
+ services.create!(category: 'issue_tracker', project_id: project.id, active: true)
+ end
+ end
+
+ def create_disabled_external_issue_tracker_integrations!(*projects)
+ projects.each do |project|
+ services.create!(category: 'issue_tracker', project_id: project.id, active: false)
+ end
+ end
+
+ def create_active_other_integrations!(*projects)
+ projects.each do |project|
+ services.create!(category: 'not_an_issue_tracker', project_id: project.id, active: true)
+ end
+ end
+
+ it 'sets `projects.has_external_issue_tracker` correctly' do
+ allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(false)
+
+ project_with_an_external_issue_tracker_1,
+ project_with_an_external_issue_tracker_2,
+ project_with_only_a_disabled_external_issue_tracker_1,
+ project_with_only_a_disabled_external_issue_tracker_2,
+ project_without_any_external_issue_trackers_1,
+ project_without_any_external_issue_trackers_2 = create_projects!(6)
+
+ create_active_external_issue_tracker_integrations!(
+ project_with_an_external_issue_tracker_1,
+ project_with_an_external_issue_tracker_2
+ )
+
+ create_disabled_external_issue_tracker_integrations!(
+ project_with_an_external_issue_tracker_1,
+ project_with_an_external_issue_tracker_2,
+ project_with_only_a_disabled_external_issue_tracker_1,
+ project_with_only_a_disabled_external_issue_tracker_2
+ )
+
+ create_active_other_integrations!(
+ project_with_an_external_issue_tracker_1,
+ project_with_an_external_issue_tracker_2,
+ project_without_any_external_issue_trackers_1,
+ project_without_any_external_issue_trackers_2
+ )
+
+ # PG triggers on the services table added in
+ # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51852 will have set
+ # the `has_external_issue_tracker` columns to correct data when the services
+ # records were created above.
+ #
+ # We set the `has_external_issue_tracker` columns for projects to incorrect
+ # data manually below to emulate projects in a state before the PG
+ # triggers were added.
+ project_with_an_external_issue_tracker_2.update!(has_external_issue_tracker: false)
+ project_with_only_a_disabled_external_issue_tracker_2.update!(has_external_issue_tracker: true)
+ project_without_any_external_issue_trackers_2.update!(has_external_issue_tracker: true)
+
+ migrate!
+
+ expected_true = [
+ project_with_an_external_issue_tracker_1,
+ project_with_an_external_issue_tracker_2
+ ].each(&:reload).map(&:has_external_issue_tracker)
+
+ expected_not_true = [
+ project_without_any_external_issue_trackers_1,
+ project_without_any_external_issue_trackers_2,
+ project_with_only_a_disabled_external_issue_tracker_1,
+ project_with_only_a_disabled_external_issue_tracker_2
+ ].each(&:reload).map(&:has_external_issue_tracker)
+
+ expect(expected_true).to all(eq(true))
+ expect(expected_not_true).to all(be_falsey)
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 799b85edf3c..c1e1d878e95 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -3266,23 +3266,8 @@ RSpec.describe User do
create(:group_group_link, shared_group: private_group, shared_with_group: other_group)
end
- context 'when shared_group_membership_auth is enabled' do
- before do
- stub_feature_flags(shared_group_membership_auth: user)
- end
-
- it { is_expected.to include shared_group }
- it { is_expected.not_to include other_group }
- end
-
- context 'when shared_group_membership_auth is disabled' do
- before do
- stub_feature_flags(shared_group_membership_auth: false)
- end
-
- it { is_expected.not_to include shared_group }
- it { is_expected.not_to include other_group }
- end
+ it { is_expected.to include shared_group }
+ it { is_expected.not_to include other_group }
end
end