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:
-rw-r--r--.gitlab-ci.yml11
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/issues/constants.js1
-rw-r--r--app/assets/javascripts/issues/show/components/app.vue4
-rw-r--r--app/assets/javascripts/notes/components/sidebar_subscription.vue4
-rw-r--r--app/assets/javascripts/pipelines/components/parsing_utils.js7
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue7
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_content.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/labels/labels_select_widget/labels_select_root.vue6
-rw-r--r--app/assets/javascripts/sidebar/components/sidebar_dropdown.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue4
-rw-r--r--app/assets/javascripts/sidebar/constants.js18
-rw-r--r--app/assets/javascripts/super_sidebar/components/help_center.vue55
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue7
-rw-r--r--app/controllers/concerns/issuable_actions.rb2
-rw-r--r--app/controllers/projects/issues_controller.rb2
-rw-r--r--app/graphql/mutations/issues/move.rb2
-rw-r--r--app/graphql/mutations/work_items/delete.rb2
-rw-r--r--app/graphql/types/issue_type.rb18
-rw-r--r--app/helpers/sidebars_helper.rb5
-rw-r--r--app/helpers/web_hooks/web_hooks_helper.rb10
-rw-r--r--app/models/concerns/web_hooks/has_web_hooks.rb46
-rw-r--r--app/models/hooks/project_hook.rb16
-rw-r--r--app/models/project.rb1
-rw-r--r--app/models/repository.rb30
-rw-r--r--app/models/user_synced_attributes_metadata.rb16
-rw-r--r--app/serializers/triggered_pipeline_entity.rb3
-rw-r--r--app/services/design_management/copy_design_collection/copy_service.rb6
-rw-r--r--app/services/issuable/clone/base_service.rb5
-rw-r--r--app/services/issuable/destroy_service.rb5
-rw-r--r--app/services/issues/update_service.rb4
-rw-r--r--app/services/tasks_to_be_done/base_service.rb4
-rw-r--r--app/services/work_items/delete_task_service.rb2
-rw-r--r--app/views/projects/mirrors/_branch_filter.html.haml6
-rw-r--r--app/views/projects/mirrors/_mirror_repos.html.haml7
-rw-r--r--app/views/projects/mirrors/_mirror_repos_list.html.haml4
-rw-r--r--app/views/projects/mirrors/_mirror_repos_push.html.haml1
-rw-r--r--app/workers/tasks_to_be_done/create_worker.rb2
-rw-r--r--config/initializers/1_settings.rb1
-rw-r--r--config/initializers/countries.rb54
-rw-r--r--data/deprecations/15-9-deprecate-external-field-in-graphql-release-asset-link.yml13
-rw-r--r--db/post_migrate/20230209131808_recount_epic_cache_counts_v3.rb29
-rw-r--r--db/schema_migrations/202302091318081
-rw-r--r--doc/api/graphql/reference/index.md2
-rw-r--r--doc/update/deprecations.md16
-rw-r--r--doc/user/admin_area/settings/email.md4
-rw-r--r--doc/user/project/issues/managing_issues.md2
-rw-r--r--doc/user/project/merge_requests/index.md2
-rw-r--r--lib/api/issues.rb6
-rw-r--r--lib/api/merge_requests.rb2
-rw-r--r--lib/gitlab/auth/o_auth/user.rb2
-rw-r--r--lib/gitlab/background_migration/third_recount_epic_cache_counts.rb20
-rw-r--r--lib/gitlab/git/repository.rb16
-rw-r--r--lib/gitlab/gitaly_client/operation_service.rb9
-rw-r--r--lib/gitlab/redis/cache.rb13
-rw-r--r--lib/gitlab/redis/rate_limiting.rb6
-rw-r--r--lib/gitlab/redis/repository_cache.rb3
-rw-r--r--lib/gitlab/slash_commands/issue_move.rb2
-rw-r--r--locale/gitlab.pot39
-rw-r--r--spec/features/projects/settings/repository_settings_spec.rb1
-rw-r--r--spec/frontend/commit/mock_data.js14
-rw-r--r--spec/frontend/fixtures/pipelines.rb11
-rw-r--r--spec/frontend/issues/show/components/app_spec.js12
-rw-r--r--spec/frontend/issues/show/components/locked_warning_spec.js4
-rw-r--r--spec/frontend/pipelines/pipelines_table_spec.js8
-rw-r--r--spec/frontend/pipelines/utils_spec.js37
-rw-r--r--spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js6
-rw-r--r--spec/frontend/super_sidebar/components/help_center_spec.js15
-rw-r--r--spec/frontend/super_sidebar/mock_data.js3
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js8
-rw-r--r--spec/frontend/vue_merge_request_widget/components/report_widget_container_spec.js8
-rw-r--r--spec/frontend/vue_merge_request_widget/mock_data.js90
-rw-r--r--spec/frontend/vue_shared/components/confidentiality_badge_spec.js8
-rw-r--r--spec/helpers/sidebars_helper_spec.rb5
-rw-r--r--spec/helpers/web_hooks/web_hooks_helper_spec.rb18
-rw-r--r--spec/lib/gitlab/auth/o_auth/user_spec.rb61
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb42
-rw-r--r--spec/lib/gitlab/gitaly_client/operation_service_spec.rb43
-rw-r--r--spec/lib/gitlab/redis/cache_spec.rb17
-rw-r--r--spec/lib/gitlab/redis/rate_limiting_spec.rb17
-rw-r--r--spec/lib/gitlab/redis/repository_cache_spec.rb15
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb2
-rw-r--r--spec/migrations/recount_epic_cache_counts_v3_spec.rb32
-rw-r--r--spec/models/hooks/project_hook_spec.rb106
-rw-r--r--spec/models/hooks/web_hook_spec.rb2
-rw-r--r--spec/models/project_spec.rb8
-rw-r--r--spec/models/repository_spec.rb57
-rw-r--r--spec/requests/api/graphql/issue/issue_spec.rb41
-rw-r--r--spec/requests/api/graphql/project/alert_management/alerts_spec.rb2
-rw-r--r--spec/serializers/issue_entity_spec.rb2
-rw-r--r--spec/serializers/pipeline_details_entity_spec.rb1
-rw-r--r--spec/services/issuable/destroy_service_spec.rb2
-rw-r--r--spec/services/issues/clone_service_spec.rb2
-rw-r--r--spec/services/issues/move_service_spec.rb2
-rw-r--r--spec/services/tasks_to_be_done/base_service_spec.rb2
-rw-r--r--spec/services/work_items/delete_service_spec.rb2
-rw-r--r--spec/support/shared_examples/models/concerns/web_hooks/has_web_hooks_shared_examples.rb107
-rw-r--r--spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb24
-rw-r--r--spec/workers/tasks_to_be_done/create_worker_spec.rb2
100 files changed, 1165 insertions, 253 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 9fa296be455..8644659198a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -104,15 +104,20 @@ workflow:
# If `$GITLAB_INTERNAL` isn't set, don't create a pipeline.
- if: '$GITLAB_INTERNAL == null'
when: never
- # For stable, auto-deploy, and security branches, create a pipeline.
- - if: '$CI_COMMIT_BRANCH =~ /^[\d-]+-stable(-ee)?$/'
+ # For last 3 stable branches, create a pipeline with failure notifications.
+ - if: '$CI_COMMIT_BRANCH =~ /^15-[6|7|8]-stable(-ee)?$/'
variables:
<<: *ruby2-variables
NOTIFY_PIPELINE_FAILURE_CHANNEL: "releases"
- PIPELINE_NAME: 'Ruby 2 $CI_COMMIT_BRANCH branch pipeline'
CREATE_INCIDENT_FOR_PIPELINE_FAILURE: "true"
BROKEN_BRANCH_INCIDENTS_PROJECT: "gitlab-org/release/tasks"
BROKEN_BRANCH_INCIDENTS_PROJECT_TOKEN: "${BROKEN_STABLE_INCIDENTS_PROJECT_TOKEN}"
+ PIPELINE_NAME: 'Ruby 2 $CI_COMMIT_BRANCH branch pipeline'
+ # For stable, auto-deploy, and security branches, create a pipeline.
+ - if: '$CI_COMMIT_BRANCH =~ /^[\d-]+-stable(-ee)?$/'
+ variables:
+ <<: *ruby2-variables
+ PIPELINE_NAME: 'Ruby 2 $CI_COMMIT_BRANCH branch pipeline'
- if: '$CI_COMMIT_BRANCH =~ /^\d+-\d+-auto-deploy-\d+$/'
variables:
<<: *ruby2-variables
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index ab1f709de26..d29ac054b2c 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-994a537cefce6044125fa420cd7536a6efda0618
+a41c0909051714c0d6b58f8000589f82a66804a5
diff --git a/app/assets/javascripts/issues/constants.js b/app/assets/javascripts/issues/constants.js
index 10d1a2d0dbf..ba05dd731f7 100644
--- a/app/assets/javascripts/issues/constants.js
+++ b/app/assets/javascripts/issues/constants.js
@@ -6,6 +6,7 @@ export const STATUS_REOPENED = 'reopened';
export const TITLE_LENGTH_MAX = 255;
+export const TYPE_EPIC = 'epic';
export const TYPE_ISSUE = 'issue';
export const IssuableStatusText = {
diff --git a/app/assets/javascripts/issues/show/components/app.vue b/app/assets/javascripts/issues/show/components/app.vue
index a0db1ddcf6f..decb559ee81 100644
--- a/app/assets/javascripts/issues/show/components/app.vue
+++ b/app/assets/javascripts/issues/show/components/app.vue
@@ -4,8 +4,8 @@ import Visibility from 'visibilityjs';
import { createAlert } from '~/flash';
import {
IssuableStatusText,
- IssuableType,
STATUS_CLOSED,
+ TYPE_EPIC,
TYPE_ISSUE,
WorkspaceType,
} from '~/issues/constants';
@@ -277,7 +277,7 @@ export default {
return IssuableStatusText[this.issuableStatus];
},
shouldShowStickyHeader() {
- return [TYPE_ISSUE, IssuableType.Epic].includes(this.issuableType);
+ return [TYPE_ISSUE, TYPE_EPIC].includes(this.issuableType);
},
},
created() {
diff --git a/app/assets/javascripts/notes/components/sidebar_subscription.vue b/app/assets/javascripts/notes/components/sidebar_subscription.vue
index a33f4364097..2a0a3d5414f 100644
--- a/app/assets/javascripts/notes/components/sidebar_subscription.vue
+++ b/app/assets/javascripts/notes/components/sidebar_subscription.vue
@@ -1,6 +1,6 @@
<script>
import { mapActions } from 'vuex';
-import { IssuableType, TYPE_ISSUE } from '~/issues/constants';
+import { TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants';
import { fetchPolicies } from '~/lib/graphql';
import { confidentialityQueries } from '~/sidebar/constants';
import { defaultClient as gqlClient } from '~/graphql_shared/issuable_client';
@@ -28,7 +28,7 @@ export default {
},
},
created() {
- if (this.issuableType !== TYPE_ISSUE && this.issuableType !== IssuableType.Epic) {
+ if (this.issuableType !== TYPE_ISSUE && this.issuableType !== TYPE_EPIC) {
return;
}
diff --git a/app/assets/javascripts/pipelines/components/parsing_utils.js b/app/assets/javascripts/pipelines/components/parsing_utils.js
index 83ba984ce07..e158f8809b5 100644
--- a/app/assets/javascripts/pipelines/components/parsing_utils.js
+++ b/app/assets/javascripts/pipelines/components/parsing_utils.js
@@ -171,11 +171,12 @@ export const generateColumnsFromLayersListBare = ({ stages, stagesLookup }, pipe
export const generateColumnsFromLayersListMemoized = memoize(generateColumnsFromLayersListBare);
-// TODO: handle REST / MR values
-// See https://gitlab.com/gitlab-org/gitlab/-/issues/367547
export const keepLatestDownstreamPipelines = (downstreamPipelines = []) => {
- // handles GraphQL
return downstreamPipelines.filter((pipeline) => {
+ if (pipeline.source_job) {
+ return !pipeline?.source_job?.retried || false;
+ }
+
return !pipeline?.sourceJob?.retried || false;
});
};
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
index ed32d643c0e..365572f194b 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
@@ -2,6 +2,7 @@
import { GlTableLite, GlTooltipDirective } from '@gitlab/ui';
import { s__, __ } from '~/locale';
import Tracking from '~/tracking';
+import { keepLatestDownstreamPipelines } from '~/pipelines/components/parsing_utils';
import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
import eventHub from '../../event_hub';
import { TRACKING_CATEGORIES } from '../../constants';
@@ -115,6 +116,10 @@ export default {
eventHub.$off('openConfirmationModal', this.setModalData);
},
methods: {
+ getDownstreamPipelines(pipeline) {
+ const downstream = pipeline.triggered;
+ return keepLatestDownstreamPipelines(downstream);
+ },
setModalData(data) {
this.pipelineId = data.pipeline.id;
this.pipeline = data.pipeline;
@@ -171,7 +176,7 @@ export default {
<template #cell(stages)="{ item }">
<pipeline-mini-graph
- :downstream-pipelines="item.triggered"
+ :downstream-pipelines="getDownstreamPipelines(item)"
:pipeline-path="item.path"
:stages="item.details.stages"
:update-dropdown="updateGraphDropdown"
diff --git a/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_content.vue b/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_content.vue
index 6afaee91d7a..1eeb725d5c9 100644
--- a/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_content.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_content.vue
@@ -1,7 +1,7 @@
<script>
import { GlIcon, GlAlert, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale';
-import { IssuableType, WorkspaceType } from '~/issues/constants';
+import { TYPE_EPIC, WorkspaceType } from '~/issues/constants';
import { confidentialityInfoText } from '~/vue_shared/constants';
export default {
@@ -25,7 +25,7 @@ export default {
computed: {
confidentialBodyText() {
return confidentialityInfoText(
- this.issuableType === IssuableType.Epic ? WorkspaceType.group : WorkspaceType.project,
+ this.issuableType === TYPE_EPIC ? WorkspaceType.group : WorkspaceType.project,
this.issuableType,
);
},
diff --git a/app/assets/javascripts/sidebar/components/labels/labels_select_widget/labels_select_root.vue b/app/assets/javascripts/sidebar/components/labels/labels_select_widget/labels_select_root.vue
index 60361b7de67..bf916e26a15 100644
--- a/app/assets/javascripts/sidebar/components/labels/labels_select_widget/labels_select_root.vue
+++ b/app/assets/javascripts/sidebar/components/labels/labels_select_widget/labels_select_root.vue
@@ -4,7 +4,7 @@ import issuableLabelsSubscription from 'ee_else_ce/sidebar/queries/issuable_labe
import { MutationOperationMode, getIdFromGraphQLId } from '~/graphql_shared/utils';
import { createAlert } from '~/flash';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import { IssuableType, TYPE_ISSUE } from '~/issues/constants';
+import { IssuableType, TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants';
import { __ } from '~/locale';
import { issuableLabelsQueries } from '../../../constants';
@@ -269,7 +269,7 @@ export default {
...updateVariables,
operationMode: MutationOperationMode.Replace,
};
- case IssuableType.Epic:
+ case TYPE_EPIC:
return {
iid: currentIid,
groupPath: this.fullPath,
@@ -330,7 +330,7 @@ export default {
labelIds: [labelId],
operationMode: MutationOperationMode.Remove,
};
- case IssuableType.Epic:
+ case TYPE_EPIC:
return {
iid: this.iid,
removeLabelIds: [getIdFromGraphQLId(labelId)],
diff --git a/app/assets/javascripts/sidebar/components/sidebar_dropdown.vue b/app/assets/javascripts/sidebar/components/sidebar_dropdown.vue
index eaf87e4d4e0..d68e4974ea4 100644
--- a/app/assets/javascripts/sidebar/components/sidebar_dropdown.vue
+++ b/app/assets/javascripts/sidebar/components/sidebar_dropdown.vue
@@ -8,7 +8,7 @@ import {
GlSearchBoxByType,
} from '@gitlab/ui';
import { kebabCase, snakeCase } from 'lodash';
-import { IssuableType, TYPE_ISSUE, WorkspaceType } from '~/issues/constants';
+import { IssuableType, TYPE_EPIC, TYPE_ISSUE, WorkspaceType } from '~/issues/constants';
import { __ } from '~/locale';
import {
defaultEpicSort,
@@ -155,7 +155,7 @@ export default {
},
isEpic() {
// MV to EE https://gitlab.com/gitlab-org/gitlab/-/issues/345311
- return this.issuableAttribute === IssuableType.Epic;
+ return this.issuableAttribute === TYPE_EPIC;
},
issuableAttributeQuery() {
return this.issuableAttributesQueries[this.issuableAttribute];
diff --git a/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue b/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
index 994b96b7f30..5df65c4aaaf 100644
--- a/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
+++ b/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
@@ -3,7 +3,7 @@ import { GlButton, GlIcon, GlLink, GlPopover, GlTooltipDirective } from '@gitlab
import { kebabCase, snakeCase } from 'lodash';
import { createAlert } from '~/flash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { IssuableType, TYPE_ISSUE } from '~/issues/constants';
+import { IssuableType, TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants';
import { timeFor } from '~/lib/utils/datetime_utility';
import { __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -153,7 +153,7 @@ export default {
},
isEpic() {
// MV to EE https://gitlab.com/gitlab-org/gitlab/-/issues/345311
- return this.issuableAttribute === IssuableType.Epic;
+ return this.issuableAttribute === TYPE_EPIC;
},
formatIssuableAttribute() {
return {
diff --git a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue
index 0fba1cb5e4e..cbe839d1112 100644
--- a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue
+++ b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue
@@ -1,7 +1,7 @@
<script>
import { GlDropdownForm, GlIcon, GlLoadingIcon, GlToggle, GlTooltipDirective } from '@gitlab/ui';
import { createAlert } from '~/flash';
-import { IssuableType } from '~/issues/constants';
+import { IssuableType, TYPE_EPIC } from '~/issues/constants';
import { isLoggedIn } from '~/lib/utils/common_utils';
import { __, sprintf } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -105,7 +105,7 @@ export default {
return ICON_ON;
},
parentIsGroup() {
- return this.issuableType === IssuableType.Epic;
+ return this.issuableType === TYPE_EPIC;
},
subscribeDisabledDescription() {
return sprintf(__('Disabled by %{parent} owner'), {
diff --git a/app/assets/javascripts/sidebar/constants.js b/app/assets/javascripts/sidebar/constants.js
index b2d6a111a0e..14491226b15 100644
--- a/app/assets/javascripts/sidebar/constants.js
+++ b/app/assets/javascripts/sidebar/constants.js
@@ -3,7 +3,7 @@ import { s__, __, sprintf } from '~/locale';
import updateIssueLabelsMutation from '~/boards/graphql/issue_set_labels.mutation.graphql';
import userSearchQuery from '~/graphql_shared/queries/users_search.query.graphql';
import userSearchWithMRPermissionsQuery from '~/graphql_shared/queries/users_search_with_mr_permissions.graphql';
-import { IssuableType, TYPE_ISSUE, WorkspaceType } from '~/issues/constants';
+import { IssuableType, TYPE_EPIC, TYPE_ISSUE, WorkspaceType } from '~/issues/constants';
import updateAlertAssigneesMutation from '~/vue_shared/alert_details/graphql/mutations/alert_set_assignees.mutation.graphql';
import updateTestCaseLabelsMutation from './components/labels/labels_select_widget/graphql/update_test_case_labels.mutation.graphql';
import epicLabelsQuery from './components/labels/labels_select_widget/graphql/epic_labels.query.graphql';
@@ -86,7 +86,7 @@ export const participantsQueries = {
[IssuableType.MergeRequest]: {
query: getMergeRequestParticipants,
},
- [IssuableType.Epic]: {
+ [TYPE_EPIC]: {
query: epicParticipantsQuery,
},
[IssuableType.Alert]: {
@@ -109,7 +109,7 @@ export const confidentialityQueries = {
query: issueConfidentialQuery,
mutation: updateIssueConfidentialMutation,
},
- [IssuableType.Epic]: {
+ [TYPE_EPIC]: {
query: epicConfidentialQuery,
mutation: updateEpicConfidentialMutation,
},
@@ -122,7 +122,7 @@ export const referenceQueries = {
[IssuableType.MergeRequest]: {
query: mergeRequestReferenceQuery,
},
- [IssuableType.Epic]: {
+ [TYPE_EPIC]: {
query: epicReferenceQuery,
},
};
@@ -147,7 +147,7 @@ export const issuableLabelsQueries = {
mutation: updateMergeRequestLabelsMutation,
mutationName: 'mergeRequestSetLabels',
},
- [IssuableType.Epic]: {
+ [TYPE_EPIC]: {
issuableQuery: epicLabelsQuery,
mutation: updateEpicLabelsMutation,
mutationName: 'updateEpic',
@@ -182,7 +182,7 @@ export const subscribedQueries = {
query: issueSubscribedQuery,
mutation: updateIssueSubscriptionMutation,
},
- [IssuableType.Epic]: {
+ [TYPE_EPIC]: {
query: epicSubscribedQuery,
mutation: updateEpicSubscriptionMutation,
},
@@ -211,14 +211,14 @@ export const dueDateQueries = {
query: issueDueDateQuery,
mutation: updateIssueDueDateMutation,
},
- [IssuableType.Epic]: {
+ [TYPE_EPIC]: {
query: epicDueDateQuery,
mutation: updateEpicDueDateMutation,
},
};
export const startDateQueries = {
- [IssuableType.Epic]: {
+ [TYPE_EPIC]: {
query: epicStartDateQuery,
mutation: updateEpicStartDateMutation,
},
@@ -283,7 +283,7 @@ export const issuableAttributesQueries = {
};
export const todoQueries = {
- [IssuableType.Epic]: {
+ [TYPE_EPIC]: {
query: epicTodoQuery,
},
[TYPE_ISSUE]: {
diff --git a/app/assets/javascripts/super_sidebar/components/help_center.vue b/app/assets/javascripts/super_sidebar/components/help_center.vue
index ed4b0d4772e..8e7c7efa631 100644
--- a/app/assets/javascripts/super_sidebar/components/help_center.vue
+++ b/app/assets/javascripts/super_sidebar/components/help_center.vue
@@ -1,5 +1,6 @@
<script>
import { GlBadge, GlButton, GlDisclosureDropdown, GlDisclosureDropdownGroup } from '@gitlab/ui';
+import GitlabVersionCheckBadge from '~/gitlab_version_check/components/gitlab_version_check_badge.vue';
import { helpPagePath } from '~/helpers/help_page_helper';
import { PROMO_URL } from 'jh_else_ce/lib/utils/url_utility';
import { __ } from '~/locale';
@@ -11,6 +12,7 @@ export default {
GlButton,
GlDisclosureDropdown,
GlDisclosureDropdownGroup,
+ GitlabVersionCheckBadge,
},
i18n: {
help: __('Help'),
@@ -21,6 +23,7 @@ export default {
contribute: __('Contribute to GitLab'),
feedback: __('Provide feedback'),
shortcuts: __('Keyboard shortcuts'),
+ version: __('Your GitLab version'),
whatsnew: __("What's new"),
},
props: {
@@ -35,9 +38,18 @@ export default {
};
},
computed: {
- items() {
- return [
- {
+ itemGroups() {
+ return {
+ versionCheck: {
+ items: [
+ {
+ text: this.$options.i18n.version,
+ href: helpPagePath('update/index'),
+ version: `${this.sidebarData.gitlab_version.major}.${this.sidebarData.gitlab_version.minor}`,
+ },
+ ],
+ },
+ helpLinks: {
items: [
{ text: this.$options.i18n.help, href: helpPagePath() },
{ text: this.$options.i18n.support, href: this.sidebarData.support_path },
@@ -51,7 +63,7 @@ export default {
{ text: this.$options.i18n.feedback, href: 'https://about.gitlab.com/submit-feedback' },
],
},
- {
+ helpActions: {
items: [
{
text: this.$options.i18n.shortcuts,
@@ -67,7 +79,10 @@ export default {
},
].filter(Boolean),
},
- ];
+ };
+ },
+ updateSeverity() {
+ return this.sidebarData.gitlab_version_check?.severity;
},
},
methods: {
@@ -120,8 +135,34 @@ export default {
</gl-button>
</template>
- <gl-disclosure-dropdown-group :group="items[0]" />
- <gl-disclosure-dropdown-group :group="items[1]" bordered @action="handleAction">
+ <gl-disclosure-dropdown-group
+ v-if="sidebarData.show_version_check"
+ :group="itemGroups.versionCheck"
+ >
+ <template #list-item="{ item }">
+ <a
+ :href="item.href"
+ tabindex="-1"
+ class="gl-display-flex gl-flex-direction-column gl-line-height-24 gl-text-gray-900 gl-hover-text-gray-900 gl-hover-text-decoration-none"
+ >
+ <span class="gl-font-sm gl-font-weight-bold">
+ {{ item.text }}
+ <gl-emoji data-name="rocket" />
+ </span>
+ <span>
+ <span class="gl-mr-2">{{ item.version }}</span>
+ <gitlab-version-check-badge v-if="updateSeverity" :status="updateSeverity" size="sm" />
+ </span>
+ </a>
+ </template>
+ </gl-disclosure-dropdown-group>
+
+ <gl-disclosure-dropdown-group
+ :group="itemGroups.helpLinks"
+ :bordered="sidebarData.show_version_check"
+ />
+
+ <gl-disclosure-dropdown-group :group="itemGroups.helpActions" bordered @action="handleAction">
<template #list-item="{ item }">
<button
tabindex="-1"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
index 1093e7cb2ee..2dec95c3fda 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
@@ -11,6 +11,7 @@ import {
import SafeHtml from '~/vue_shared/directives/safe_html';
import { s__, n__ } from '~/locale';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import { keepLatestDownstreamPipelines } from '~/pipelines/components/parsing_utils';
import PipelineArtifacts from '~/pipelines/components/pipelines_list/pipelines_artifacts.vue';
import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
@@ -86,6 +87,10 @@ export default {
},
},
computed: {
+ downstreamPipelines() {
+ const downstream = this.pipeline.triggered;
+ return keepLatestDownstreamPipelines(downstream);
+ },
hasPipeline() {
return this.pipeline && Object.keys(this.pipeline).length > 0;
},
@@ -274,7 +279,7 @@ export default {
<span class="gl-align-items-center gl-display-inline-flex">
<pipeline-mini-graph
v-if="pipeline.details.stages"
- :downstream-pipelines="pipeline.triggered"
+ :downstream-pipelines="downstreamPipelines"
:is-merge-train="isMergeTrain"
:pipeline-path="pipeline.path"
:stages="pipeline.details.stages"
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index 980275147ae..0ec23a3265f 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -90,7 +90,7 @@ module IssuableActions
end
def destroy
- Issuable::DestroyService.new(project: issuable.project, current_user: current_user).execute(issuable)
+ Issuable::DestroyService.new(container: issuable.project, current_user: current_user).execute(issuable)
name = issuable.human_class_name
flash[:notice] = "The #{name} was successfully deleted."
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 3731257033d..97d0fa43529 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -191,7 +191,7 @@ class Projects::IssuesController < Projects::ApplicationController
new_project = Project.find(params[:move_to_project_id])
return render_404 unless issue.can_move?(current_user, new_project)
- @issue = ::Issues::MoveService.new(project: project, current_user: current_user).execute(issue, new_project)
+ @issue = ::Issues::MoveService.new(container: project, current_user: current_user).execute(issue, new_project)
end
respond_to do |format|
diff --git a/app/graphql/mutations/issues/move.rb b/app/graphql/mutations/issues/move.rb
index 63bc9dabbf9..ef3f70c78b9 100644
--- a/app/graphql/mutations/issues/move.rb
+++ b/app/graphql/mutations/issues/move.rb
@@ -18,7 +18,7 @@ module Mutations
target_project = resolve_project(full_path: target_project_path).sync
begin
- moved_issue = ::Issues::MoveService.new(project: source_project, current_user: current_user).execute(issue, target_project)
+ moved_issue = ::Issues::MoveService.new(container: source_project, current_user: current_user).execute(issue, target_project)
rescue ::Issues::MoveService::MoveError => e
errors = e.message
end
diff --git a/app/graphql/mutations/work_items/delete.rb b/app/graphql/mutations/work_items/delete.rb
index 4b0067d40d4..ec0244fa65e 100644
--- a/app/graphql/mutations/work_items/delete.rb
+++ b/app/graphql/mutations/work_items/delete.rb
@@ -20,7 +20,7 @@ module Mutations
work_item = authorized_find!(id: id)
result = ::WorkItems::DeleteService.new(
- project: work_item.project,
+ container: work_item.project,
current_user: current_user
).execute(work_item)
diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb
index fa102c7b496..de0d5cc417d 100644
--- a/app/graphql/types/issue_type.rb
+++ b/app/graphql/types/issue_type.rb
@@ -65,6 +65,13 @@ module Types
field :merge_requests_count, GraphQL::Types::Int, null: false,
description: 'Number of merge requests that close the issue on merge.',
resolver: Resolvers::MergeRequestsCountResolver
+
+ field :related_merge_requests, Types::MergeRequestType.connection_type,
+ null: true,
+ description: 'Merge requests related to the issue. This field can only be resolved for one issue in any single request.' do
+ extension ::Gitlab::Graphql::Limit::FieldCallCount, limit: 1
+ end
+
field :relative_position, GraphQL::Types::Int, null: true,
description: 'Relative position of the issue (used for positioning in epic tree and issue boards).'
field :upvotes, GraphQL::Types::Int,
@@ -180,6 +187,17 @@ module Types
Gitlab::Graphql::Loaders::BatchModelLoader.new(Issue, object.duplicated_to_id).find
end
+ def related_merge_requests
+ # rubocop: disable CodeReuse/ActiveRecord
+ MergeRequest.where(
+ id: ::Issues::ReferencedMergeRequestsService.new(project: object.project, current_user: current_user)
+ .execute(object)
+ .first
+ .map(&:id)
+ )
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+
def discussion_locked
!!object.discussion_locked
end
diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb
index 733792cdf0f..27020738515 100644
--- a/app/helpers/sidebars_helper.rb
+++ b/app/helpers/sidebars_helper.rb
@@ -48,7 +48,10 @@ module SidebarsHelper
support_path: support_url,
display_whats_new: display_whats_new?,
whats_new_most_recent_release_items_count: whats_new_most_recent_release_items_count,
- whats_new_version_digest: whats_new_version_digest
+ whats_new_version_digest: whats_new_version_digest,
+ show_version_check: show_version_check?,
+ gitlab_version: Gitlab.version_info,
+ gitlab_version_check: gitlab_version_check
}
end
diff --git a/app/helpers/web_hooks/web_hooks_helper.rb b/app/helpers/web_hooks/web_hooks_helper.rb
index bda9bf58fb7..514db6ba8a2 100644
--- a/app/helpers/web_hooks/web_hooks_helper.rb
+++ b/app/helpers/web_hooks/web_hooks_helper.rb
@@ -2,8 +2,6 @@
module WebHooks
module WebHooksHelper
- EXPIRY_TTL = 1.hour
-
def show_project_hook_failed_callout?(project:)
return false if project_hook_page?
return false unless current_user
@@ -12,17 +10,11 @@ module WebHooks
# Assumes include of Users::CalloutsHelper
return false if web_hook_disabled_dismissed?(project)
- any_project_hook_failed?(project) # Most expensive query last
+ project.fetch_web_hook_failure
end
private
- def any_project_hook_failed?(project)
- Rails.cache.fetch("any_web_hook_failed:#{project.id}", expires_in: EXPIRY_TTL) do
- ProjectHook.for_projects(project).disabled.exists?
- end
- end
-
def project_hook_page?
current_controller?('projects/hooks') || current_controller?('projects/hook_logs')
end
diff --git a/app/models/concerns/web_hooks/has_web_hooks.rb b/app/models/concerns/web_hooks/has_web_hooks.rb
new file mode 100644
index 00000000000..161ce106b9b
--- /dev/null
+++ b/app/models/concerns/web_hooks/has_web_hooks.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module WebHooks
+ module HasWebHooks
+ extend ActiveSupport::Concern
+
+ WEB_HOOK_CACHE_EXPIRY = 1.hour
+
+ def any_hook_failed?
+ hooks.disabled.exists?
+ end
+
+ def web_hook_failure_redis_key
+ "any_web_hook_failed:#{id}"
+ end
+
+ def last_failure_redis_key
+ "web_hooks:last_failure:project-#{id}"
+ end
+
+ def get_web_hook_failure
+ Gitlab::Redis::SharedState.with do |redis|
+ current = redis.get(web_hook_failure_redis_key)
+
+ Gitlab::Utils.to_boolean(current) if current
+ end
+ end
+
+ def fetch_web_hook_failure
+ Gitlab::Redis::SharedState.with do |_redis|
+ current = get_web_hook_failure
+ next current unless current.nil?
+
+ cache_web_hook_failure
+ end
+ end
+
+ def cache_web_hook_failure(state = any_hook_failed?)
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set(web_hook_failure_redis_key, state.to_s, ex: WEB_HOOK_CACHE_EXPIRY)
+
+ state
+ end
+ end
+ end
+end
diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb
index 81122c3ea10..8e9a74a68d0 100644
--- a/app/models/hooks/project_hook.rb
+++ b/app/models/hooks/project_hook.rb
@@ -46,14 +46,18 @@ class ProjectHook < WebHook
override :update_last_failure
def update_last_failure
- return if executable?
+ if executable?
+ project.cache_web_hook_failure if project.get_web_hook_failure # may need update
+ else
+ project.cache_web_hook_failure(true) # definitely failing, no need to check
- key = "web_hooks:last_failure:project-#{project_id}"
- time = Time.current.utc.iso8601
+ Gitlab::Redis::SharedState.with do |redis|
+ last_failure_key = project.last_failure_redis_key
+ time = Time.current.utc.iso8601
+ prev = redis.get(last_failure_key)
- Gitlab::Redis::SharedState.with do |redis|
- prev = redis.get(key)
- redis.set(key, time) if !prev || prev < time
+ redis.set(last_failure_key, time) if !prev || prev < time
+ end
end
end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 27be84d787e..63ea19202ce 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -41,6 +41,7 @@ class Project < ApplicationRecord
include BlocksUnsafeSerialization
include Subquery
include IssueParent
+ include WebHooks::HasWebHooks
extend Gitlab::Cache::RequestCache
extend Gitlab::Utils::Override
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 31c65555cb2..d15f2a430fa 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -872,10 +872,24 @@ class Repository
end
def merge(user, source_sha, merge_request, message)
+ merge_to_branch(user,
+ source_sha: source_sha,
+ target_branch: merge_request.target_branch,
+ message: message) do |commit_id|
+ merge_request.update_and_mark_in_progress_merge_commit_sha(commit_id)
+ nil # Return value does not matter.
+ end
+ end
+
+ def merge_to_branch(user, source_sha:, target_branch:, message:, target_sha: nil)
with_cache_hooks do
- raw_repository.merge(user, source_sha, merge_request.target_branch, message) do |commit_id|
- merge_request.update_and_mark_in_progress_merge_commit_sha(commit_id)
- nil # Return value does not matter.
+ raw_repository.merge(user,
+ source_sha: source_sha,
+ target_branch: target_branch,
+ message: message,
+ target_sha: target_sha
+ ) do |commit_id|
+ yield commit_id if block_given?
end
end
end
@@ -884,13 +898,19 @@ class Repository
raw.delete_refs(...)
end
- def ff_merge(user, source, target_branch, merge_request: nil)
+ def ff_merge(user, source, target_branch, target_sha: nil, merge_request: nil)
their_commit_id = commit(source)&.id
raise 'Invalid merge source' if their_commit_id.nil?
merge_request&.update_and_mark_in_progress_merge_commit_sha(their_commit_id)
- with_cache_hooks { raw.ff_merge(user, their_commit_id, target_branch) }
+ with_cache_hooks do
+ raw.ff_merge(user,
+ source_sha: their_commit_id,
+ target_branch: target_branch,
+ target_sha: target_sha
+ )
+ end
end
def revert(
diff --git a/app/models/user_synced_attributes_metadata.rb b/app/models/user_synced_attributes_metadata.rb
index 5aacf11b1cb..4cceffda19e 100644
--- a/app/models/user_synced_attributes_metadata.rb
+++ b/app/models/user_synced_attributes_metadata.rb
@@ -14,7 +14,7 @@ class UserSyncedAttributesMetadata < ApplicationRecord
def read_only_attributes
return [] unless sync_profile_from_provider?
- SYNCABLE_ATTRIBUTES.select { |key| synced?(key) }
+ self.class.syncable_attributes.select { |key| synced?(key) }
end
def synced?(attribute)
@@ -25,6 +25,20 @@ class UserSyncedAttributesMetadata < ApplicationRecord
write_attribute("#{attribute}_synced", value)
end
+ class << self
+ def syncable_attributes
+ return SYNCABLE_ATTRIBUTES if sync_name?
+
+ SYNCABLE_ATTRIBUTES - %i[name]
+ end
+
+ private
+
+ def sync_name?
+ Gitlab.config.ldap.sync_name
+ end
+ end
+
private
def sync_profile_from_provider?
diff --git a/app/serializers/triggered_pipeline_entity.rb b/app/serializers/triggered_pipeline_entity.rb
index 9fdadb322bf..d8d0e576565 100644
--- a/app/serializers/triggered_pipeline_entity.rb
+++ b/app/serializers/triggered_pipeline_entity.rb
@@ -15,6 +15,9 @@ class TriggeredPipelineEntity < Grape::Entity
expose :name do |pipeline|
pipeline.source_job&.name
end
+ expose :retried do |pipeline|
+ pipeline.source_job&.retried
+ end
end
expose :path do |pipeline|
diff --git a/app/services/design_management/copy_design_collection/copy_service.rb b/app/services/design_management/copy_design_collection/copy_service.rb
index 3bc30f62a81..8074a193bbf 100644
--- a/app/services/design_management/copy_design_collection/copy_service.rb
+++ b/app/services/design_management/copy_design_collection/copy_service.rb
@@ -128,9 +128,9 @@ module DesignManagement
target_repository.raw.merge(
git_user,
- source_sha,
- merge_branch,
- 'CopyDesignCollectionService finalize merge'
+ source_sha: source_sha,
+ target_branch: merge_branch,
+ message: 'CopyDesignCollectionService finalize merge'
) { nil }
target_design_collection.end_copy!
diff --git a/app/services/issuable/clone/base_service.rb b/app/services/issuable/clone/base_service.rb
index 3c13944cfbc..3d0abece1a2 100644
--- a/app/services/issuable/clone/base_service.rb
+++ b/app/services/issuable/clone/base_service.rb
@@ -7,6 +7,11 @@ module Issuable
alias_method :old_project, :project
+ # TODO: this is to be removed once we get to rename the IssuableBaseService project param to container
+ def initialize(container:, current_user: nil, params: {})
+ super(project: container, current_user: current_user, params: params)
+ end
+
def execute(original_entity, target_parent)
@original_entity = original_entity
@target_parent = target_parent
diff --git a/app/services/issuable/destroy_service.rb b/app/services/issuable/destroy_service.rb
index 6aab56f0f68..4c3e518d62b 100644
--- a/app/services/issuable/destroy_service.rb
+++ b/app/services/issuable/destroy_service.rb
@@ -2,6 +2,11 @@
module Issuable
class DestroyService < IssuableBaseService
+ # TODO: this is to be removed once we get to rename the IssuableBaseService project param to container
+ def initialize(container:, current_user: nil, params: {})
+ super(project: container, current_user: current_user, params: params)
+ end
+
def execute(issuable)
after_destroy(issuable) if issuable.destroy
end
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index d43df0da3fd..a185ff374ed 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -109,7 +109,7 @@ module Issues
target_project != issue.project
update(issue)
- Issues::MoveService.new(project: project, current_user: current_user).execute(issue, target_project)
+ Issues::MoveService.new(container: project, current_user: current_user).execute(issue, target_project)
end
private
@@ -139,7 +139,7 @@ module Issues
# we've pre-empted this from running in #execute, so let's go ahead and update the Issue now.
update(issue)
- Issues::CloneService.new(project: project, current_user: current_user).execute(issue, target_project, with_notes: with_notes)
+ Issues::CloneService.new(container: project, current_user: current_user).execute(issue, target_project, with_notes: with_notes)
end
def create_merge_request_from_quick_action
diff --git a/app/services/tasks_to_be_done/base_service.rb b/app/services/tasks_to_be_done/base_service.rb
index a5648ad10c4..c868ebcc463 100644
--- a/app/services/tasks_to_be_done/base_service.rb
+++ b/app/services/tasks_to_be_done/base_service.rb
@@ -4,14 +4,14 @@ module TasksToBeDone
class BaseService < ::IssuableBaseService
LABEL_PREFIX = 'tasks to be done'
- def initialize(project:, current_user:, assignee_ids: [])
+ def initialize(container:, current_user:, assignee_ids: [])
params = {
assignee_ids: assignee_ids,
title: title,
description: description,
add_labels: label_name
}
- super(project: project, current_user: current_user, params: params)
+ super(project: container, current_user: current_user, params: params)
end
def execute
diff --git a/app/services/work_items/delete_task_service.rb b/app/services/work_items/delete_task_service.rb
index 2a82a993b71..3d66716543a 100644
--- a/app/services/work_items/delete_task_service.rb
+++ b/app/services/work_items/delete_task_service.rb
@@ -25,7 +25,7 @@ module WorkItems
break ::ServiceResponse.error(message: replacement_result.errors, http_status: 422) if replacement_result.error?
delete_result = ::WorkItems::DeleteService.new(
- project: @task.project,
+ container: @task.project,
current_user: @current_user
).execute(@task)
diff --git a/app/views/projects/mirrors/_branch_filter.html.haml b/app/views/projects/mirrors/_branch_filter.html.haml
new file mode 100644
index 00000000000..b9db9898d49
--- /dev/null
+++ b/app/views/projects/mirrors/_branch_filter.html.haml
@@ -0,0 +1,6 @@
+.form-check.gl-mb-3
+ = check_box_tag :only_protected_branches, '1', false, class: 'js-mirror-protected form-check-input'
+ = label_tag :only_protected_branches, _('Mirror only protected branches'), class: 'form-check-label'
+ .form-text.text-muted
+ = _('If enabled, only protected branches will be mirrored.')
+ = link_to _('Learn more.'), help_page_path('user/project/repository/mirror/index.md', anchor: 'mirror-only-protected-branches'), target: '_blank', rel: 'noopener noreferrer'
diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml
index f4e57450aa1..4cfe463fa38 100644
--- a/app/views/projects/mirrors/_mirror_repos.html.haml
+++ b/app/views/projects/mirrors/_mirror_repos.html.haml
@@ -27,12 +27,7 @@
= render 'projects/mirrors/mirror_repos_form', f: f
- .form-check.gl-mb-3
- = check_box_tag :only_protected_branches, '1', false, class: 'js-mirror-protected form-check-input'
- = label_tag :only_protected_branches, _('Mirror only protected branches'), class: 'form-check-label'
- .form-text.text-muted
- = _('If enabled, only protected branches will be mirrored.')
- = link_to _('Learn more.'), help_page_path('user/project/repository/mirror/index.md', anchor: 'mirror-only-protected-branches'), target: '_blank', rel: 'noopener noreferrer'
+ = render 'projects/mirrors/branch_filter'
.panel-footer
= f.submit _('Mirror repository'), class: 'js-mirror-submit', name: :update_remote_mirror, pajamas_button: true, data: { qa_selector: 'mirror_repository_button' }
diff --git a/app/views/projects/mirrors/_mirror_repos_list.html.haml b/app/views/projects/mirrors/_mirror_repos_list.html.haml
index fb8133e6de8..46833b5986b 100644
--- a/app/views/projects/mirrors/_mirror_repos_list.html.haml
+++ b/app/views/projects/mirrors/_mirror_repos_list.html.haml
@@ -26,7 +26,9 @@
- @project.remote_mirrors.each_with_index do |mirror, index|
- next if mirror.new_record?
%tr.rspec-mirrored-repository-row{ class: ('bg-secondary' if mirror.disabled?), data: { qa_selector: 'mirrored_repository_row_container' } }
- %td{ data: { qa_selector: 'mirror_repository_url_content' } }= mirror.safe_url || _('Invalid URL')
+ %td{ data: { qa_selector: 'mirror_repository_url_content' } }
+ = mirror.safe_url || _('Invalid URL')
+ = render_if_exists 'projects/mirrors/mirror_branches_setting_badge', record: mirror
%td= _('Push')
%td
= mirror.last_update_started_at.present? ? time_ago_with_tooltip(mirror.last_update_started_at) : _('Never')
diff --git a/app/views/projects/mirrors/_mirror_repos_push.html.haml b/app/views/projects/mirrors/_mirror_repos_push.html.haml
index 339c5d82919..136f504084e 100644
--- a/app/views/projects/mirrors/_mirror_repos_push.html.haml
+++ b/app/views/projects/mirrors/_mirror_repos_push.html.haml
@@ -4,6 +4,7 @@
= rm_f.hidden_field :enabled, value: '1'
= rm_f.hidden_field :url, class: 'js-mirror-url-hidden', required: true, pattern: "(#{protocols}):\/\/.+"
= rm_f.hidden_field :only_protected_branches, class: 'js-mirror-protected-hidden'
+ = render_if_exists partial: 'projects/mirrors/branch_name_regex', locals: { f: rm_f }
= rm_f.hidden_field :keep_divergent_refs, class: 'js-mirror-keep-divergent-refs-hidden'
= render partial: 'projects/mirrors/ssh_host_keys', locals: { f: rm_f }
= render partial: 'projects/mirrors/authentication_method', locals: { f: rm_f }
diff --git a/app/workers/tasks_to_be_done/create_worker.rb b/app/workers/tasks_to_be_done/create_worker.rb
index 0953f190fd0..d3824ceb4ae 100644
--- a/app/workers/tasks_to_be_done/create_worker.rb
+++ b/app/workers/tasks_to_be_done/create_worker.rb
@@ -17,7 +17,7 @@ module TasksToBeDone
member_task.tasks_to_be_done.each do |task|
service_class(task)
- .new(project: project, current_user: current_user, assignee_ids: assignee_ids)
+ .new(container: project, current_user: current_user, assignee_ids: assignee_ids)
.execute
end
end
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index a2798bfda4d..1927d15d72b 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -27,6 +27,7 @@ end
# backwards compatibility, we only have one host
if Settings.ldap['enabled'] || Rails.env.test?
+ Settings.ldap['sync_name'] = true if Settings.ldap['sync_name'].nil?
if Settings.ldap['host'].present?
# We detected old LDAP configuration syntax. Update the config to make it
# look like it was entered with the new syntax.
diff --git a/config/initializers/countries.rb b/config/initializers/countries.rb
index e22637f459c..98d06c9a893 100644
--- a/config/initializers/countries.rb
+++ b/config/initializers/countries.rb
@@ -8,57 +8,11 @@ end
# This overrides the display name for Ukraine to 'Ukraine (except the Crimea, Donetsk, and Luhansk regions)'
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/374946
# To be removed after https://gitlab.com/gitlab-org/gitlab/issues/14784 is implemented
-# Data fetched is based on https://github.com/countries/countries/blob/master/lib/countries/data/countries/UA.yaml
ISO3166::Data.register(
- continent: "Europe",
- address_format: "|-
- {{recipient}}
- {{street}}
- {{city}} {{region_short}}
- {{postalcode}}
- {{country}}",
- alpha2: "UA",
- alpha3: "UKR",
- country_code: '380',
- international_prefix: '810',
- ioc: "UKR",
- gec: "UP",
- name: "Ukraine (except the Crimea, Donetsk, and Luhansk regions)",
- national_destination_code_lengths: [2],
- national_number_lengths: [8, 9],
- national_prefix: '8',
- number: '804',
- region: "Europe",
- subregion: "Eastern Europe",
- world_region: "EMEA",
- un_locode: "UA",
- nationality: "Ukrainian",
- vat_rates: {
- standard: 20
- },
- reduced: [7],
- super_reduced: {
- parking: { postal_code: true }
- },
- unofficial_names: %w(Ukraine Ucrania ウクライナ Oekraïne Украина Україна Украіна),
- languages_official: ["uk"],
- languages_spoken: ["uk"],
- geo: {
- latitude: 48.379433,
- latitude_dec: '48.92656326293945',
- longitude: 31.16558,
- longitude_dec: '31.47578239440918',
- max_latitude: 52.37958099999999,
- max_longitude: 40.2285809,
- min_latitude: 44.2924,
- min_longitude: 22.137159,
- bounds: {
- northeast: { lat: 52.37958099999999, lng: 40.2285809 },
- southwest: { lat: 44.2924, lng: 22.137159 }
- }
- },
- currency_code: "UAH",
- start_of_week: "monday"
+ ISO3166::Data.new('UA')
+ .call
+ .deep_symbolize_keys
+ .merge({ name: 'Ukraine (except the Crimea, Donetsk, and Luhansk regions)' })
)
# Updating the display name of Taiwan, from `Taiwan, Province of China` to `Taiwan`
diff --git a/data/deprecations/15-9-deprecate-external-field-in-graphql-release-asset-link.yml b/data/deprecations/15-9-deprecate-external-field-in-graphql-release-asset-link.yml
new file mode 100644
index 00000000000..3cc9505a0b1
--- /dev/null
+++ b/data/deprecations/15-9-deprecate-external-field-in-graphql-release-asset-link.yml
@@ -0,0 +1,13 @@
+- title: "External field in GraphQL ReleaseAssetLink type"
+ announcement_milestone: "15.9"
+ announcement_date: "2023-02-22"
+ removal_milestone: "16.0"
+ removal_date: "2023-05-22"
+ breaking_change: true
+ reporter: ahmed.hemdan
+ body: |
+ In the [GraphQL API](https://docs.gitlab.com/ee/api/graphql), the `external` field of [`ReleaseAssetLink` type](https://docs.gitlab.com/ee/api/graphql/reference/index.html#releaseassetlink) was used to indicate whether a [release link](https://docs.gitlab.com/ee/user/project/releases/release_fields.html#links) is internal or external to your GitLab instance.
+ As of GitLab 15.9, we treat all release links as external, and therefore, this field is deprecated in GitLab 15.9, and will be removed in GitLab 16.0.
+ To avoid any disruptions to your workflow, please stop using the `external` field because it will be removed and will not be replaced.
+ stage: Release
+ issue_url:
diff --git a/db/post_migrate/20230209131808_recount_epic_cache_counts_v3.rb b/db/post_migrate/20230209131808_recount_epic_cache_counts_v3.rb
new file mode 100644
index 00000000000..6252a300453
--- /dev/null
+++ b/db/post_migrate/20230209131808_recount_epic_cache_counts_v3.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+class RecountEpicCacheCountsV3 < Gitlab::Database::Migration[2.1]
+ MIGRATION = 'ThirdRecountEpicCacheCounts'
+ DELAY_INTERVAL = 2.minutes.to_i
+ BATCH_SIZE = 200
+ MAX_BATCH_SIZE = 1000
+ SUB_BATCH_SIZE = 20
+
+ disable_ddl_transaction!
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ def up
+ queue_batched_background_migration(
+ MIGRATION,
+ :epics,
+ :id,
+ job_interval: DELAY_INTERVAL,
+ batch_size: BATCH_SIZE,
+ max_batch_size: MAX_BATCH_SIZE,
+ sub_batch_size: SUB_BATCH_SIZE,
+ gitlab_schema: :gitlab_main
+ )
+ end
+
+ def down
+ delete_batched_background_migration(MIGRATION, :epics, :id, [])
+ end
+end
diff --git a/db/schema_migrations/20230209131808 b/db/schema_migrations/20230209131808
new file mode 100644
index 00000000000..ba90b9dcd50
--- /dev/null
+++ b/db/schema_migrations/20230209131808
@@ -0,0 +1 @@
+48c3039b24ab063a550419d3883b3c6308709e0ef9eacc0f4f1cdc3c99fb4148 \ No newline at end of file
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 7a8c383a341..6765adcca87 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -13263,6 +13263,7 @@ Relationship between an epic and an issue.
| <a id="epicissuenotes"></a>`notes` | [`NoteConnection!`](#noteconnection) | All notes on this noteable. (see [Connections](#connections)) |
| <a id="epicissueparticipants"></a>`participants` | [`UserCoreConnection`](#usercoreconnection) | List of participants in the issue. (see [Connections](#connections)) |
| <a id="epicissueprojectid"></a>`projectId` | [`Int!`](#int) | ID of the issue project. |
+| <a id="epicissuerelatedmergerequests"></a>`relatedMergeRequests` | [`MergeRequestConnection`](#mergerequestconnection) | Merge requests related to the issue. This field can only be resolved for one issue in any single request. (see [Connections](#connections)) |
| <a id="epicissuerelatedvulnerabilities"></a>`relatedVulnerabilities` | [`VulnerabilityConnection`](#vulnerabilityconnection) | Related vulnerabilities of the issue. (see [Connections](#connections)) |
| <a id="epicissuerelationpath"></a>`relationPath` | [`String`](#string) | URI path of the epic-issue relation. |
| <a id="epicissuerelativeposition"></a>`relativePosition` | [`Int`](#int) | Relative position of the issue (used for positioning in epic tree and issue boards). |
@@ -15009,6 +15010,7 @@ Describes an issuable resource link for incident issues.
| <a id="issuenotes"></a>`notes` | [`NoteConnection!`](#noteconnection) | All notes on this noteable. (see [Connections](#connections)) |
| <a id="issueparticipants"></a>`participants` | [`UserCoreConnection`](#usercoreconnection) | List of participants in the issue. (see [Connections](#connections)) |
| <a id="issueprojectid"></a>`projectId` | [`Int!`](#int) | ID of the issue project. |
+| <a id="issuerelatedmergerequests"></a>`relatedMergeRequests` | [`MergeRequestConnection`](#mergerequestconnection) | Merge requests related to the issue. This field can only be resolved for one issue in any single request. (see [Connections](#connections)) |
| <a id="issuerelatedvulnerabilities"></a>`relatedVulnerabilities` | [`VulnerabilityConnection`](#vulnerabilityconnection) | Related vulnerabilities of the issue. (see [Connections](#connections)) |
| <a id="issuerelativeposition"></a>`relativePosition` | [`Int`](#int) | Relative position of the issue (used for positioning in epic tree and issue boards). |
| <a id="issueseverity"></a>`severity` | [`IssuableSeverity`](#issuableseverity) | Severity level of the incident. |
diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md
index 5d124bfe045..59c2d1d923b 100644
--- a/doc/update/deprecations.md
+++ b/doc/update/deprecations.md
@@ -161,6 +161,22 @@ During the transition to the GitLab Observability UI, we will migrate the [GitLa
<div class="deprecation removal-160 breaking-change">
+### External field in GraphQL ReleaseAssetLink type
+
+Planned removal: GitLab <span class="removal-milestone">16.0</span> <span class="removal-date"></span>
+
+WARNING:
+This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
+Review the details carefully before upgrading.
+
+In the [GraphQL API](https://docs.gitlab.com/ee/api/graphql), the `external` field of [`ReleaseAssetLink` type](https://docs.gitlab.com/ee/api/graphql/reference/index.html#releaseassetlink) was used to indicate whether a [release link](https://docs.gitlab.com/ee/user/project/releases/release_fields.html#links) is internal or external to your GitLab instance.
+As of GitLab 15.9, we treat all release links as external, and therefore, this field is deprecated in GitLab 15.9, and will be removed in GitLab 16.0.
+To avoid any disruptions to your workflow, please stop using the `external` field because it will be removed and will not be replaced.
+
+</div>
+
+<div class="deprecation removal-160 breaking-change">
+
### External field in Releases and Release Links APIs
Planned removal: GitLab <span class="removal-milestone">16.0</span> <span class="removal-date"></span>
diff --git a/doc/user/admin_area/settings/email.md b/doc/user/admin_area/settings/email.md
index 3cdb045d19d..484f51d8739 100644
--- a/doc/user/admin_area/settings/email.md
+++ b/doc/user/admin_area/settings/email.md
@@ -86,9 +86,7 @@ To disable these notifications:
### Custom additional text in deactivation emails **(FREE SELF)**
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/355964) in GitLab 15.9
-[with a flag](../../../administration/feature_flags.md) named `deactivation_email_additional_text`.
-Disabled by default.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/355964) in GitLab 15.9 [with a flag](../../../administration/feature_flags.md) named `deactivation_email_additional_text`. Disabled by default.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available, ask an
diff --git a/doc/user/project/issues/managing_issues.md b/doc/user/project/issues/managing_issues.md
index fb7995b1915..158249c66a6 100644
--- a/doc/user/project/issues/managing_issues.md
+++ b/doc/user/project/issues/managing_issues.md
@@ -172,7 +172,7 @@ To do it:
issues.each do |issue|
if issue.state != "closed" && issue.moved_to.nil?
- Issues::MoveService.new(project: project, current_user: admin_user).execute(issue, target_project)
+ Issues::MoveService.new(container: project, current_user: admin_user).execute(issue, target_project)
else
puts "issue with id: #{issue.id} and title: #{issue.title} was not moved"
end
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index 0276f3d98b6..a633366cc62 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -378,5 +378,5 @@ with a backup of the instance ready to be restored, just in case.
u = User.find_by_username('<username>')
p = Project.find_by_full_path('<namespace/project>')
m = p.merge_requests.find_by(iid: <iid>)
-Issuable::DestroyService.new(project: m.project, current_user: u).execute(m)
+Issuable::DestroyService.new(container: m.project, current_user: u).execute(m)
```
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 7ba055467fe..e8eb4bb1ddf 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -376,7 +376,7 @@ module API
not_found!('Project') unless new_project
begin
- issue = ::Issues::MoveService.new(project: user_project, current_user: current_user).execute(issue, new_project)
+ issue = ::Issues::MoveService.new(container: user_project, current_user: current_user).execute(issue, new_project)
present issue, with: Entities::Issue, current_user: current_user, project: user_project
rescue ::Issues::MoveService::MoveError => error
render_api_error!(error.message, 400)
@@ -403,7 +403,7 @@ module API
not_found!('Project') unless target_project
begin
- issue = ::Issues::CloneService.new(project: user_project, current_user: current_user)
+ issue = ::Issues::CloneService.new(container: user_project, current_user: current_user)
.execute(issue, target_project, with_notes: params[:with_notes])
present issue, with: Entities::Issue, current_user: current_user, project: target_project
rescue ::Issues::CloneService::CloneError => error
@@ -424,7 +424,7 @@ module API
authorize!(:destroy_issue, issue)
destroy_conditionally!(issue) do |issue|
- Issuable::DestroyService.new(project: user_project, current_user: current_user).execute(issue)
+ Issuable::DestroyService.new(container: user_project, current_user: current_user).execute(issue)
end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index d4a445edfe2..cd46b442b68 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -312,7 +312,7 @@ module API
authorize!(:destroy_merge_request, merge_request)
destroy_conditionally!(merge_request) do |merge_request|
- Issuable::DestroyService.new(project: user_project, current_user: current_user).execute(merge_request)
+ Issuable::DestroyService.new(container: user_project, current_user: current_user).execute(merge_request)
end
end
diff --git a/lib/gitlab/auth/o_auth/user.rb b/lib/gitlab/auth/o_auth/user.rb
index 242390c3e89..01e126ec2f5 100644
--- a/lib/gitlab/auth/o_auth/user.rb
+++ b/lib/gitlab/auth/o_auth/user.rb
@@ -258,7 +258,7 @@ module Gitlab
metadata = gl_user.build_user_synced_attributes_metadata
if sync_profile_from_provider?
- UserSyncedAttributesMetadata::SYNCABLE_ATTRIBUTES.each do |key|
+ UserSyncedAttributesMetadata.syncable_attributes.each do |key|
if auth_hash.has_attribute?(key) && gl_user.sync_attribute?(key)
gl_user.public_send("#{key}=".to_sym, auth_hash.public_send(key)) # rubocop:disable GitlabSecurity/PublicSend
metadata.set_attribute_synced(key, true)
diff --git a/lib/gitlab/background_migration/third_recount_epic_cache_counts.rb b/lib/gitlab/background_migration/third_recount_epic_cache_counts.rb
new file mode 100644
index 00000000000..24080a06c46
--- /dev/null
+++ b/lib/gitlab/background_migration/third_recount_epic_cache_counts.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # rubocop: disable Style/Documentation
+ class ThirdRecountEpicCacheCounts < Gitlab::BackgroundMigration::BatchedMigrationJob
+ feature_category :database
+
+ def perform; end
+ end
+ # rubocop: enable Style/Documentation
+ end
+end
+
+# rubocop: disable Layout/LineLength
+# we just want to re-enqueue the previous BackfillEpicCacheCounts migration,
+# because it's a EE-only migation and it's a module, we just prepend new
+# RecountEpicCacheCounts with existing batched migration module (which is same in both cases)
+Gitlab::BackgroundMigration::ThirdRecountEpicCacheCounts.prepend_mod_with('Gitlab::BackgroundMigration::BackfillEpicCacheCounts')
+# rubocop: enable Layout/LineLength
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 678e298c378..e054b6df98f 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -652,15 +652,23 @@ module Gitlab
end
end
- def merge(user, source_sha, target_branch, message, &block)
+ def merge(user, source_sha:, target_branch:, message:, target_sha: nil, &block)
wrapped_gitaly_errors do
- gitaly_operation_client.user_merge_branch(user, source_sha, target_branch, message, &block)
+ gitaly_operation_client.user_merge_branch(user,
+ source_sha: source_sha,
+ target_branch: target_branch,
+ message: message,
+ target_sha: target_sha,
+ &block)
end
end
- def ff_merge(user, source_sha, target_branch)
+ def ff_merge(user, source_sha:, target_branch:, target_sha: nil)
wrapped_gitaly_errors do
- gitaly_operation_client.user_ff_branch(user, source_sha, target_branch)
+ gitaly_operation_client.user_ff_branch(user,
+ source_sha: source_sha,
+ target_branch: target_branch,
+ target_sha: target_sha)
end
end
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index 6865e76d4bb..313334737c0 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -152,7 +152,7 @@ module Gitlab
response.commit_id
end
- def user_merge_branch(user, source_sha, target_branch, message)
+ def user_merge_branch(user, source_sha:, target_branch:, message:, target_sha: nil)
request_enum = QueueEnumerator.new
response_enum = gitaly_client_call(
@repository.storage,
@@ -168,6 +168,7 @@ module Gitlab
user: Gitlab::Git::User.from_gitlab(user).to_gitaly,
commit_id: source_sha,
branch: encode_binary(target_branch),
+ expected_old_oid: target_sha,
message: encode_binary(message),
timestamp: Google::Protobuf::Timestamp.new(seconds: Time.now.utc.to_i)
)
@@ -184,7 +185,6 @@ module Gitlab
raise Gitlab::Git::CommitError, 'failed to apply merge to branch' unless branch_update.commit_id.present?
Gitlab::Git::OperationService::BranchUpdate.from_gitaly(branch_update)
-
rescue GRPC::BadStatus => e
detailed_error = GitalyClient.decode_detailed_error(e)
@@ -207,12 +207,13 @@ module Gitlab
request_enum.close
end
- def user_ff_branch(user, source_sha, target_branch)
+ def user_ff_branch(user, source_sha:, target_branch:, target_sha: nil)
request = Gitaly::UserFFBranchRequest.new(
repository: @gitaly_repo,
user: Gitlab::Git::User.from_gitlab(user).to_gitaly,
commit_id: source_sha,
- branch: encode_binary(target_branch)
+ branch: encode_binary(target_branch),
+ expected_old_oid: target_sha
)
response = gitaly_client_call(
diff --git a/lib/gitlab/redis/cache.rb b/lib/gitlab/redis/cache.rb
index ba3af3e7a6f..647573e59fe 100644
--- a/lib/gitlab/redis/cache.rb
+++ b/lib/gitlab/redis/cache.rb
@@ -2,6 +2,16 @@
module Gitlab
module Redis
+ # Match signature in
+ # https://github.com/rails/rails/blob/v6.1.7.2/activesupport/lib/active_support/cache/redis_cache_store.rb#L59
+ ERROR_HANDLER = ->(method:, returning:, exception:) do
+ Gitlab::ErrorTracking.log_exception(
+ exception,
+ method: method,
+ returning: returning.inspect
+ )
+ end
+
class Cache < ::Gitlab::Redis::Wrapper
CACHE_NAMESPACE = 'cache:gitlab'
@@ -12,7 +22,8 @@ module Gitlab
redis: pool,
compress: Gitlab::Utils.to_boolean(ENV.fetch('ENABLE_REDIS_CACHE_COMPRESSION', '1')),
namespace: CACHE_NAMESPACE,
- expires_in: default_ttl_seconds
+ expires_in: default_ttl_seconds,
+ error_handler: ::Gitlab::Redis::ERROR_HANDLER
}
end
diff --git a/lib/gitlab/redis/rate_limiting.rb b/lib/gitlab/redis/rate_limiting.rb
index 3a9fb63a495..12710bafbea 100644
--- a/lib/gitlab/redis/rate_limiting.rb
+++ b/lib/gitlab/redis/rate_limiting.rb
@@ -10,7 +10,11 @@ module Gitlab
end
def cache_store
- @cache_store ||= ActiveSupport::Cache::RedisCacheStore.new(redis: pool, namespace: Cache::CACHE_NAMESPACE)
+ @cache_store ||= ActiveSupport::Cache::RedisCacheStore.new(
+ redis: pool,
+ namespace: Cache::CACHE_NAMESPACE,
+ error_handler: ::Gitlab::Redis::ERROR_HANDLER
+ )
end
private
diff --git a/lib/gitlab/redis/repository_cache.rb b/lib/gitlab/redis/repository_cache.rb
index d8d434ae062..6c7bc8c41d5 100644
--- a/lib/gitlab/redis/repository_cache.rb
+++ b/lib/gitlab/redis/repository_cache.rb
@@ -14,7 +14,8 @@ module Gitlab
redis: pool,
compress: Gitlab::Utils.to_boolean(ENV.fetch('ENABLE_REDIS_CACHE_COMPRESSION', '1')),
namespace: Cache::CACHE_NAMESPACE,
- expires_in: Cache.default_ttl_seconds
+ expires_in: Cache.default_ttl_seconds,
+ error_handler: ::Gitlab::Redis::ERROR_HANDLER
)
end
end
diff --git a/lib/gitlab/slash_commands/issue_move.rb b/lib/gitlab/slash_commands/issue_move.rb
index 9f10da247d7..e42cdd0d433 100644
--- a/lib/gitlab/slash_commands/issue_move.rb
+++ b/lib/gitlab/slash_commands/issue_move.rb
@@ -29,7 +29,7 @@ module Gitlab
return Gitlab::SlashCommands::Presenters::Access.new.not_found
end
- new_issue = ::Issues::MoveService.new(project: project, current_user: current_user)
+ new_issue = ::Issues::MoveService.new(container: project, current_user: current_user)
.execute(old_issue, target_project)
presenter(new_issue).present(old_issue)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index db87d0353cd..ef899e10756 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -4107,6 +4107,9 @@ msgstr ""
msgid "All Members"
msgstr ""
+msgid "All branch names must match %{link_start}this regular expression%{link_end}. If empty, any branch name is allowed."
+msgstr ""
+
msgid "All branches"
msgstr ""
@@ -8740,6 +8743,9 @@ msgstr ""
msgid "Choose which Git strategy to use when fetching the project."
msgstr ""
+msgid "Choose which branches should be mirrored"
+msgstr ""
+
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
@@ -16773,6 +16779,9 @@ msgstr ""
msgid "Exactly one of %{attributes} is required"
msgstr ""
+msgid "Example"
+msgstr ""
+
msgid "Example: (feature|hotfix)\\/*"
msgstr ""
@@ -21088,6 +21097,9 @@ msgstr ""
msgid "IdentityVerification|For added security, you'll need to verify your identity in a few quick steps."
msgstr ""
+msgid "IdentityVerification|For added security, you'll need to verify your identity."
+msgstr ""
+
msgid "IdentityVerification|For added security, you'll need to verify your identity. We've sent a verification code to %{email}"
msgstr ""
@@ -21187,6 +21199,9 @@ msgstr ""
msgid "IdentityVerification|We sent a new code to +%{phoneNumber}"
msgstr ""
+msgid "IdentityVerification|We've sent a verification code to %{email}"
+msgstr ""
+
msgid "IdentityVerification|We've sent a verification code to +%{phoneNumber}"
msgstr ""
@@ -21235,6 +21250,9 @@ msgstr ""
msgid "If disabled, only administrators can configure repository mirroring."
msgstr ""
+msgid "If enabled, all branches will be mirrored."
+msgstr ""
+
msgid "If enabled, only protected branches will be mirrored."
msgstr ""
@@ -27329,6 +27347,12 @@ msgstr ""
msgid "Minutes"
msgstr ""
+msgid "Mirror all branches"
+msgstr ""
+
+msgid "Mirror branches"
+msgstr ""
+
msgid "Mirror direction"
msgstr ""
@@ -27341,6 +27365,9 @@ msgstr ""
msgid "Mirror settings are only available to GitLab administrators."
msgstr ""
+msgid "Mirror specific branches"
+msgstr ""
+
msgid "Mirror user"
msgstr ""
@@ -29481,9 +29508,6 @@ msgstr ""
msgid "OnDemandScans|Unable to fetch runner tags. Try reloading the page."
msgstr ""
-msgid "OnDemandScans|Verify"
-msgstr ""
-
msgid "OnDemandScans|Verify configuration"
msgstr ""
@@ -38614,6 +38638,9 @@ msgstr ""
msgid "SecurityReports|Activity"
msgstr ""
+msgid "SecurityReports|Add comment and dismiss"
+msgstr ""
+
msgid "SecurityReports|Add or remove projects to monitor in the security area. Projects included in this list will have their results displayed in the security dashboard and vulnerability report."
msgstr ""
@@ -40781,6 +40808,9 @@ msgstr ""
msgid "Spam log successfully submitted as ham."
msgstr ""
+msgid "Specific branches"
+msgstr ""
+
msgid "Specified URL cannot be used: \"%{reason}\""
msgstr ""
@@ -49362,6 +49392,9 @@ msgstr ""
msgid "Your GitLab instance allows anyone to register for an account, which is a security risk on public-facing GitLab instances. You should deactivate new sign ups if public users aren't expected to register for an account."
msgstr ""
+msgid "Your GitLab version"
+msgstr ""
+
msgid "Your Groups"
msgstr ""
diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb
index 6f0a3094849..a0625c93b1a 100644
--- a/spec/features/projects/settings/repository_settings_spec.rb
+++ b/spec/features/projects/settings/repository_settings_spec.rb
@@ -9,6 +9,7 @@ RSpec.describe 'Projects > Settings > Repository settings', feature_category: :p
before do
stub_feature_flags(branch_rules: false)
+ stub_feature_flags(mirror_only_branches_match_regex: false)
project.add_role(user, role)
sign_in(user)
end
diff --git a/spec/frontend/commit/mock_data.js b/spec/frontend/commit/mock_data.js
index 80f62d5bfd8..a13ef9c563e 100644
--- a/spec/frontend/commit/mock_data.js
+++ b/spec/frontend/commit/mock_data.js
@@ -1,4 +1,4 @@
-const downstream = {
+export const mockDownstreamPipelinesGraphql = ({ includeSourceJobRetried = true } = {}) => ({
nodes: [
{
id: 'gid://gitlab/Ci::Pipeline/612',
@@ -17,7 +17,7 @@ const downstream = {
},
sourceJob: {
id: 'gid://gitlab/Ci::Bridge/532',
- retried: false,
+ retried: includeSourceJobRetried ? false : null,
},
__typename: 'Pipeline',
},
@@ -38,7 +38,7 @@ const downstream = {
},
sourceJob: {
id: 'gid://gitlab/Ci::Bridge/531',
- retried: true,
+ retried: includeSourceJobRetried ? true : null,
},
__typename: 'Pipeline',
},
@@ -59,13 +59,13 @@ const downstream = {
},
sourceJob: {
id: 'gid://gitlab/Ci::Bridge/530',
- retried: true,
+ retried: includeSourceJobRetried ? true : null,
},
__typename: 'Pipeline',
},
],
__typename: 'PipelineConnection',
-};
+});
const upstream = {
id: 'gid://gitlab/Ci::Pipeline/610',
@@ -161,7 +161,7 @@ export const mockDownstreamQueryResponse = {
pipeline: {
path: '/root/ci-project/-/pipelines/790',
id: 'pipeline-1',
- downstream,
+ downstream: mockDownstreamPipelinesGraphql(),
upstream: null,
},
__typename: 'Project',
@@ -176,7 +176,7 @@ export const mockUpstreamDownstreamQueryResponse = {
pipeline: {
id: 'pipeline-1',
path: '/root/ci-project/-/pipelines/790',
- downstream,
+ downstream: mockDownstreamPipelinesGraphql(),
upstream,
},
__typename: 'Project',
diff --git a/spec/frontend/fixtures/pipelines.rb b/spec/frontend/fixtures/pipelines.rb
index 44b471a70d8..768934d6278 100644
--- a/spec/frontend/fixtures/pipelines.rb
+++ b/spec/frontend/fixtures/pipelines.rb
@@ -23,8 +23,19 @@ RSpec.describe Projects::PipelinesController, '(JavaScript fixtures)', type: :co
let!(:build_test) { create(:ci_build, pipeline: pipeline, stage: 'test') }
let!(:build_deploy_failed) { create(:ci_build, status: :failed, pipeline: pipeline, stage: 'deploy') }
+ let(:bridge) { create(:ci_bridge, pipeline: pipeline) }
+ let(:retried_bridge) { create(:ci_bridge, :retried, pipeline: pipeline) }
+
+ let(:downstream_pipeline) { create(:ci_pipeline, :with_job) }
+ let(:retried_downstream_pipeline) { create(:ci_pipeline, :with_job) }
+ let!(:ci_sources_pipeline) { create(:ci_sources_pipeline, pipeline: downstream_pipeline, source_job: bridge) }
+ let!(:retried_ci_sources_pipeline) do
+ create(:ci_sources_pipeline, pipeline: retried_downstream_pipeline, source_job: retried_bridge)
+ end
+
before do
sign_in(user)
+ project.add_developer(user)
end
it 'pipelines/pipelines.json' do
diff --git a/spec/frontend/issues/show/components/app_spec.js b/spec/frontend/issues/show/components/app_spec.js
index 7766bc62026..9fa0ce6f93d 100644
--- a/spec/frontend/issues/show/components/app_spec.js
+++ b/spec/frontend/issues/show/components/app_spec.js
@@ -8,10 +8,10 @@ import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/flash';
import {
IssuableStatusText,
- IssuableType,
STATUS_CLOSED,
STATUS_OPEN,
STATUS_REOPENED,
+ TYPE_EPIC,
TYPE_ISSUE,
} from '~/issues/constants';
import IssuableApp from '~/issues/show/components/app.vue';
@@ -485,11 +485,11 @@ describe('Issuable output', () => {
});
it.each`
- issuableType | issuableStatus | statusIcon
- ${TYPE_ISSUE} | ${STATUS_OPEN} | ${'issues'}
- ${TYPE_ISSUE} | ${STATUS_CLOSED} | ${'issue-closed'}
- ${IssuableType.Epic} | ${STATUS_OPEN} | ${'epic'}
- ${IssuableType.Epic} | ${STATUS_CLOSED} | ${'epic-closed'}
+ issuableType | issuableStatus | statusIcon
+ ${TYPE_ISSUE} | ${STATUS_OPEN} | ${'issues'}
+ ${TYPE_ISSUE} | ${STATUS_CLOSED} | ${'issue-closed'}
+ ${TYPE_EPIC} | ${STATUS_OPEN} | ${'epic'}
+ ${TYPE_EPIC} | ${STATUS_CLOSED} | ${'epic-closed'}
`(
'shows with state icon "$statusIcon" for $issuableType when status is $issuableStatus',
async ({ issuableType, issuableStatus, statusIcon }) => {
diff --git a/spec/frontend/issues/show/components/locked_warning_spec.js b/spec/frontend/issues/show/components/locked_warning_spec.js
index c9d510088bf..dd3c7c58380 100644
--- a/spec/frontend/issues/show/components/locked_warning_spec.js
+++ b/spec/frontend/issues/show/components/locked_warning_spec.js
@@ -1,7 +1,7 @@
import { GlAlert, GlLink } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { sprintf } from '~/locale';
-import { IssuableType, TYPE_ISSUE } from '~/issues/constants';
+import { TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants';
import LockedWarning, { i18n } from '~/issues/show/components/locked_warning.vue';
describe('LockedWarning component', () => {
@@ -21,7 +21,7 @@ describe('LockedWarning component', () => {
const findAlert = () => wrapper.findComponent(GlAlert);
const findLink = () => wrapper.findComponent(GlLink);
- describe.each([TYPE_ISSUE, IssuableType.Epic])('with issuableType set to %s', (issuableType) => {
+ describe.each([TYPE_ISSUE, TYPE_EPIC])('with issuableType set to %s', (issuableType) => {
let alert;
let link;
beforeEach(() => {
diff --git a/spec/frontend/pipelines/pipelines_table_spec.js b/spec/frontend/pipelines/pipelines_table_spec.js
index 9359bd9b95f..6ec8901038b 100644
--- a/spec/frontend/pipelines/pipelines_table_spec.js
+++ b/spec/frontend/pipelines/pipelines_table_spec.js
@@ -121,6 +121,14 @@ describe('Pipelines Table', () => {
expect(findPipelineMiniGraph().props('stages').length).toBe(stagesLength);
});
+ it('should render the latest downstream pipelines only', () => {
+ // component receives two downstream pipelines. one of them is already outdated
+ // because we retried the trigger job, so the mini pipeline graph will only
+ // render the newly created downstream pipeline instead
+ expect(pipeline.triggered).toHaveLength(2);
+ expect(findPipelineMiniGraph().props('downstreamPipelines')).toHaveLength(1);
+ });
+
describe('when pipeline does not have stages', () => {
beforeEach(() => {
pipeline = createMockPipeline();
diff --git a/spec/frontend/pipelines/utils_spec.js b/spec/frontend/pipelines/utils_spec.js
index 1c23a7e4fcf..51e0e0705ff 100644
--- a/spec/frontend/pipelines/utils_spec.js
+++ b/spec/frontend/pipelines/utils_spec.js
@@ -3,6 +3,7 @@ import {
makeLinksFromNodes,
filterByAncestors,
generateColumnsFromLayersListBare,
+ keepLatestDownstreamPipelines,
listByLayers,
parseData,
removeOrphanNodes,
@@ -10,6 +11,8 @@ import {
} from '~/pipelines/components/parsing_utils';
import { createNodeDict } from '~/pipelines/utils';
+import { mockDownstreamPipelinesRest } from '../vue_merge_request_widget/mock_data';
+import { mockDownstreamPipelinesGraphql } from '../commit/mock_data';
import { mockParsedGraphQLNodes, missingJob } from './components/dag/mock_data';
import { generateResponse, mockPipelineResponse } from './graph/mock_data';
@@ -159,3 +162,37 @@ describe('DAG visualization parsing utilities', () => {
});
});
});
+
+describe('linked pipeline utilities', () => {
+ describe('keepLatestDownstreamPipelines', () => {
+ it('filters data from GraphQL', () => {
+ const downstream = mockDownstreamPipelinesGraphql().nodes;
+ const latestDownstream = keepLatestDownstreamPipelines(downstream);
+
+ expect(downstream).toHaveLength(3);
+ expect(latestDownstream).toHaveLength(1);
+ });
+
+ it('filters data from REST', () => {
+ const downstream = mockDownstreamPipelinesRest();
+ const latestDownstream = keepLatestDownstreamPipelines(downstream);
+
+ expect(downstream).toHaveLength(2);
+ expect(latestDownstream).toHaveLength(1);
+ });
+
+ it('returns downstream pipelines if sourceJob.retried is null', () => {
+ const downstream = mockDownstreamPipelinesGraphql({ includeSourceJobRetried: false }).nodes;
+ const latestDownstream = keepLatestDownstreamPipelines(downstream);
+
+ expect(latestDownstream).toHaveLength(downstream.length);
+ });
+
+ it('returns downstream pipelines if source_job.retried is null', () => {
+ const downstream = mockDownstreamPipelinesRest({ includeSourceJobRetried: false });
+ const latestDownstream = keepLatestDownstreamPipelines(downstream);
+
+ expect(latestDownstream).toHaveLength(downstream.length);
+ });
+ });
+});
diff --git a/spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js b/spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js
index cda562398c5..fd8e72bac49 100644
--- a/spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js
+++ b/spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js
@@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/flash';
-import { IssuableType, TYPE_ISSUE } from '~/issues/constants';
+import { IssuableType, TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
import DropdownContents from '~/sidebar/components/labels/labels_select_widget/dropdown_contents.vue';
import DropdownValue from '~/sidebar/components/labels/labels_select_widget/dropdown_value.vue';
@@ -37,7 +37,7 @@ const errorQueryHandler = jest.fn().mockRejectedValue('Houston, we have a proble
const updateLabelsMutation = {
[TYPE_ISSUE]: updateIssueLabelsMutation,
[IssuableType.MergeRequest]: updateMergeRequestLabelsMutation,
- [IssuableType.Epic]: updateEpicLabelsMutation,
+ [TYPE_EPIC]: updateEpicLabelsMutation,
[IssuableType.TestCase]: updateTestCaseLabelsMutation,
};
@@ -215,7 +215,7 @@ describe('LabelsSelectRoot', () => {
issuableType
${TYPE_ISSUE}
${IssuableType.MergeRequest}
- ${IssuableType.Epic}
+ ${TYPE_EPIC}
${IssuableType.TestCase}
`('when updating labels for $issuableType', ({ issuableType }) => {
const label = { id: 'gid://gitlab/ProjectLabel/2' };
diff --git a/spec/frontend/super_sidebar/components/help_center_spec.js b/spec/frontend/super_sidebar/components/help_center_spec.js
index 86d4286a3f7..bc847a3e159 100644
--- a/spec/frontend/super_sidebar/components/help_center_spec.js
+++ b/spec/frontend/super_sidebar/components/help_center_spec.js
@@ -14,6 +14,8 @@ jest.mock('~/whats_new');
describe('HelpCenter component', () => {
let wrapper;
+ const GlEmoji = { template: '<img/>' };
+
const findDropdownGroup = (i = 0) => {
return wrapper.findAllComponents(GlDisclosureDropdownGroup).at(i);
};
@@ -24,6 +26,7 @@ describe('HelpCenter component', () => {
const createWrapper = (sidebarData) => {
wrapper = mountExtended(HelpCenter, {
propsData: { sidebarData },
+ stubs: { GlEmoji },
});
};
@@ -52,6 +55,18 @@ describe('HelpCenter component', () => {
]);
});
+ describe('with Gitlab version check feature enabled', () => {
+ beforeEach(() => {
+ createWrapper({ ...sidebarData, show_version_check: true });
+ });
+
+ it('shows version information as first item', () => {
+ expect(findDropdownGroup(0).props('group').items).toEqual([
+ { text: HelpCenter.i18n.version, href: helpPagePath('update/index'), version: '16.0' },
+ ]);
+ });
+ });
+
describe('showKeyboardShortcuts', () => {
beforeEach(() => {
jest.spyOn(wrapper.vm.$refs.dropdown, 'close');
diff --git a/spec/frontend/super_sidebar/mock_data.js b/spec/frontend/super_sidebar/mock_data.js
index 8467306eb34..91a2dc93a47 100644
--- a/spec/frontend/super_sidebar/mock_data.js
+++ b/spec/frontend/super_sidebar/mock_data.js
@@ -71,4 +71,7 @@ export const sidebarData = {
display_whats_new: true,
whats_new_most_recent_release_items_count: 5,
whats_new_version_digest: 1,
+ show_version_check: false,
+ gitlab_version: { major: 16, minor: 0 },
+ gitlab_version_check: { severity: 'success' },
};
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js
index 441054493f7..6a899c00b98 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js
@@ -111,6 +111,14 @@ describe('MRWidgetPipeline', () => {
expect(findPipelineMiniGraph().props('stages')).toHaveLength(stagesCount);
});
+ it('should render the latest downstream pipelines only', () => {
+ // component receives two downstream pipelines. one of them is already outdated
+ // because we retried the trigger job, so the mini pipeline graph will only
+ // render the newly created downstream pipeline instead
+ expect(mockData.pipeline.triggered).toHaveLength(2);
+ expect(findPipelineMiniGraph().props('downstreamPipelines')).toHaveLength(1);
+ });
+
describe('should render pipeline coverage information', () => {
it('should render coverage percentage', () => {
expect(findPipelineCoverage().text()).toMatch(
diff --git a/spec/frontend/vue_merge_request_widget/components/report_widget_container_spec.js b/spec/frontend/vue_merge_request_widget/components/report_widget_container_spec.js
index 2f6002f36e0..436f74d1be2 100644
--- a/spec/frontend/vue_merge_request_widget/components/report_widget_container_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/report_widget_container_spec.js
@@ -19,7 +19,13 @@ describe('app/assets/javascripts/vue_merge_request_widget/components/report_widg
expect(wrapper.isVisible()).toBe(false);
});
- it('shows the container when children have no content', async () => {
+ it('hides the container when children has only empty spaces', async () => {
+ createComponent({ slot: `<span><b>&nbsp;<br/>\t\r\n</b></span>&nbsp;` });
+ await nextTick();
+ expect(wrapper.isVisible()).toBe(false);
+ });
+
+ it('shows the container when a child has content', async () => {
createComponent({ slot: `<span><b>test</b></span>` });
await nextTick();
expect(wrapper.isVisible()).toBe(true);
diff --git a/spec/frontend/vue_merge_request_widget/mock_data.js b/spec/frontend/vue_merge_request_widget/mock_data.js
index 20d00a116bb..46e1919b0ea 100644
--- a/spec/frontend/vue_merge_request_widget/mock_data.js
+++ b/spec/frontend/vue_merge_request_widget/mock_data.js
@@ -1,5 +1,94 @@
import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
+export const mockDownstreamPipelinesRest = ({ includeSourceJobRetried = true } = {}) => [
+ {
+ id: 632,
+ user: {
+ id: 1,
+ username: 'root',
+ name: 'Administrator',
+ state: 'active',
+ avatar_url:
+ 'https://secure.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ web_url: 'https://gdk.test:3000/root',
+ show_status: false,
+ path: '/root',
+ },
+ active: false,
+ coverage: null,
+ source: 'parent_pipeline',
+ source_job: {
+ name: 'bridge_job',
+ retried: includeSourceJobRetried ? false : null,
+ },
+ path: '/kitchen-sink/bakery/-/pipelines/632',
+ details: {
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/kitchen-sink/bakery/-/pipelines/632',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ },
+ },
+ project: {
+ id: 21,
+ name: 'bakery',
+ full_path: '/kitchen-sink/bakery',
+ full_name: 'kitchen-sink / bakery',
+ refs_url: '/kitchen-sink/bakery/refs',
+ },
+ },
+ {
+ id: 633,
+ user: {
+ id: 1,
+ username: 'root',
+ name: 'Administrator',
+ state: 'active',
+ avatar_url:
+ 'https://secure.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ web_url: 'https://gdk.test:3000/root',
+ show_status: false,
+ path: '/root',
+ },
+ active: false,
+ coverage: null,
+ source: 'parent_pipeline',
+ source_job: {
+ name: 'bridge_job',
+ retried: includeSourceJobRetried ? true : null,
+ },
+ path: '/kitchen-sink/bakery/-/pipelines/633',
+ details: {
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/kitchen-sink/bakery/-/pipelines/633',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ },
+ },
+ project: {
+ id: 21,
+ name: 'bakery',
+ full_path: '/kitchen-sink/bakery',
+ full_name: 'kitchen-sink / bakery',
+ refs_url: '/kitchen-sink/bakery/refs',
+ },
+ },
+];
+
export const artifacts = [
{
text: 'result.txt',
@@ -207,6 +296,7 @@ export default {
retry_path: '/root/acets-app/pipelines/172/retry',
created_at: '2017-04-07T12:27:19.520Z',
updated_at: '2017-04-07T15:28:44.800Z',
+ triggered: mockDownstreamPipelinesRest(),
},
pipelineCoverageDelta: '15.25',
buildsWithCoverage: [
diff --git a/spec/frontend/vue_shared/components/confidentiality_badge_spec.js b/spec/frontend/vue_shared/components/confidentiality_badge_spec.js
index 127869e99b9..3f7ec156c19 100644
--- a/spec/frontend/vue_shared/components/confidentiality_badge_spec.js
+++ b/spec/frontend/vue_shared/components/confidentiality_badge_spec.js
@@ -1,7 +1,7 @@
import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { WorkspaceType, IssuableType, TYPE_ISSUE } from '~/issues/constants';
+import { WorkspaceType, TYPE_ISSUE, TYPE_EPIC } from '~/issues/constants';
import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue';
@@ -28,9 +28,9 @@ describe('ConfidentialityBadge', () => {
});
it.each`
- workspaceType | issuableType | expectedTooltip
- ${WorkspaceType.project} | ${TYPE_ISSUE} | ${'Only project members with at least the Reporter role, the author, and assignees can view or be notified about this issue.'}
- ${WorkspaceType.group} | ${IssuableType.Epic} | ${'Only group members with at least the Reporter role can view or be notified about this epic.'}
+ workspaceType | issuableType | expectedTooltip
+ ${WorkspaceType.project} | ${TYPE_ISSUE} | ${'Only project members with at least the Reporter role, the author, and assignees can view or be notified about this issue.'}
+ ${WorkspaceType.group} | ${TYPE_EPIC} | ${'Only group members with at least the Reporter role can view or be notified about this epic.'}
`(
'should render gl-badge with correct tooltip when workspaceType is $workspaceType and issuableType is $issuableType',
({ workspaceType, issuableType, expectedTooltip }) => {
diff --git a/spec/helpers/sidebars_helper_spec.rb b/spec/helpers/sidebars_helper_spec.rb
index e3e1d39b4f8..2b45f73815e 100644
--- a/spec/helpers/sidebars_helper_spec.rb
+++ b/spec/helpers/sidebars_helper_spec.rb
@@ -72,7 +72,10 @@ RSpec.describe SidebarsHelper do
support_path: helper.support_url,
display_whats_new: helper.display_whats_new?,
whats_new_most_recent_release_items_count: helper.whats_new_most_recent_release_items_count,
- whats_new_version_digest: helper.whats_new_version_digest
+ whats_new_version_digest: helper.whats_new_version_digest,
+ show_version_check: helper.show_version_check?,
+ gitlab_version: Gitlab.version_info,
+ gitlab_version_check: helper.gitlab_version_check
})
end
diff --git a/spec/helpers/web_hooks/web_hooks_helper_spec.rb b/spec/helpers/web_hooks/web_hooks_helper_spec.rb
index bcd9d2df1dc..fdd0be8095b 100644
--- a/spec/helpers/web_hooks/web_hooks_helper_spec.rb
+++ b/spec/helpers/web_hooks/web_hooks_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe WebHooks::WebHooksHelper do
+RSpec.describe WebHooks::WebHooksHelper, :clean_gitlab_redis_shared_state, feature_category: :integrations do
let_it_be_with_reload(:project) { create(:project) }
let(:current_user) { nil }
@@ -43,20 +43,12 @@ RSpec.describe WebHooks::WebHooksHelper do
expect(helper).to be_show_project_hook_failed_callout(project: project)
end
- it 'caches the DB calls until the TTL', :use_clean_rails_memory_store_caching, :request_store do
- helper.show_project_hook_failed_callout?(project: project)
-
- travel_to((described_class::EXPIRY_TTL - 1.second).from_now) do
- expect do
- helper.show_project_hook_failed_callout?(project: project)
- end.not_to exceed_query_limit(0)
+ it 'stores a value' do
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis).to receive(:set).with(anything, 'true', ex: 1.hour)
end
- travel_to((described_class::EXPIRY_TTL + 1.second).from_now) do
- expect do
- helper.show_project_hook_failed_callout?(project: project)
- end.to exceed_query_limit(0)
- end
+ helper.show_project_hook_failed_callout?(project: project)
end
end
diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb
index beeb3ca7011..a5cbad74829 100644
--- a/spec/lib/gitlab/auth/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb
@@ -418,25 +418,54 @@ RSpec.describe Gitlab::Auth::OAuth::User, feature_category: :authentication_and_
end
context "and LDAP user has an account already" do
- let!(:existing_user) { create(:omniauth_user, name: 'John Doe', email: 'john@example.com', extern_uid: dn, provider: 'ldapmain', username: 'john') }
+ context 'when sync_name is disabled' do
+ before do
+ allow(Gitlab.config.ldap).to receive(:sync_name).and_return(false)
+ end
- it "adds the omniauth identity to the LDAP account" do
- allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user)
+ let!(:existing_user) { create(:omniauth_user, name: 'John Doe', email: 'john@example.com', extern_uid: dn, provider: 'ldapmain', username: 'john') }
- oauth_user.save # rubocop:disable Rails/SaveBang
+ it "adds the omniauth identity to the LDAP account" do
+ allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user)
- expect(gl_user).to be_valid
- expect(gl_user.username).to eql 'john'
- expect(gl_user.name).to eql 'John Doe'
- expect(gl_user.email).to eql 'john@example.com'
- expect(gl_user.identities.length).to be 2
- identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
- expect(identities_as_hash).to match_array(
- [
- { provider: 'ldapmain', extern_uid: dn },
- { provider: 'twitter', extern_uid: uid }
- ]
- )
+ oauth_user.save # rubocop:disable Rails/SaveBang
+
+ expect(gl_user).to be_valid
+ expect(gl_user.username).to eql 'john'
+ expect(gl_user.name).to eql 'John Doe'
+ expect(gl_user.email).to eql 'john@example.com'
+ expect(gl_user.identities.length).to be 2
+ identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
+ expect(identities_as_hash).to match_array(
+ [
+ { provider: 'ldapmain', extern_uid: dn },
+ { provider: 'twitter', extern_uid: uid }
+ ]
+ )
+ end
+ end
+
+ context 'when sync_name is enabled' do
+ let!(:existing_user) { create(:omniauth_user, name: 'John Swift', email: 'john@example.com', extern_uid: dn, provider: 'ldapmain', username: 'john') }
+
+ it "adds the omniauth identity to the LDAP account" do
+ allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user)
+
+ oauth_user.save # rubocop:disable Rails/SaveBang
+
+ expect(gl_user).to be_valid
+ expect(gl_user.username).to eql 'john'
+ expect(gl_user.name).to eql 'John Swift'
+ expect(gl_user.email).to eql 'john@example.com'
+ expect(gl_user.identities.length).to be 2
+ identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
+ expect(identities_as_hash).to match_array(
+ [
+ { provider: 'ldapmain', extern_uid: dn },
+ { provider: 'twitter', extern_uid: uid }
+ ]
+ )
+ end
end
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index d6d0de63a3b..72043ba2a21 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -2106,16 +2106,22 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
let(:repository) { mutable_repository }
let(:source_sha) { '913c66a37b4a45b9769037c55c2d238bd0942d2e' }
let(:target_branch) { 'test-merge-target-branch' }
+ let(:target_sha) { '6d394385cf567f80a8fd85055db1ab4c5295806f' }
before do
- repository.create_branch(target_branch, '6d394385cf567f80a8fd85055db1ab4c5295806f')
+ repository.create_branch(target_branch, target_sha)
end
it 'can perform a merge' do
merge_commit_id = nil
- result = repository.merge(user, source_sha, target_branch, 'Test merge') do |commit_id|
- merge_commit_id = commit_id
- end
+ result =
+ repository.merge(user,
+ source_sha: source_sha,
+ target_branch: target_branch,
+ target_sha: target_sha,
+ message: 'Test merge') do |commit_id|
+ merge_commit_id = commit_id
+ end
expect(result.newrev).to eq(merge_commit_id)
expect(result.repo_created).to eq(false)
@@ -2124,10 +2130,15 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
it 'returns nil if there was a concurrent branch update' do
concurrent_update_id = '33f3729a45c02fc67d00adb1b8bca394b0e761d9'
- result = repository.merge(user, source_sha, target_branch, 'Test merge') do
- # This ref update should make the merge fail
- repository.write_ref(Gitlab::Git::BRANCH_REF_PREFIX + target_branch, concurrent_update_id)
- end
+ result =
+ repository.merge(user,
+ source_sha: source_sha,
+ target_branch: target_branch,
+ target_sha: target_sha,
+ message: 'Test merge') do |_commit_id|
+ # This ref update should make the merge fail
+ repository.write_ref(Gitlab::Git::BRANCH_REF_PREFIX + target_branch, concurrent_update_id)
+ end
# This 'nil' signals that the merge was not applied
expect(result).to be_nil
@@ -2147,7 +2158,13 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
repository.create_branch(target_branch, branch_head)
end
- subject { repository.ff_merge(user, source_sha, target_branch) }
+ subject do
+ repository.ff_merge(user,
+ source_sha: source_sha,
+ target_branch: target_branch,
+ target_sha: branch_head
+ )
+ end
shared_examples '#ff_merge' do
it 'performs a ff_merge' do
@@ -2159,7 +2176,7 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
end
context 'with a non-existing target branch' do
- subject { repository.ff_merge(user, source_sha, 'this-isnt-real') }
+ subject { repository.ff_merge(user, source_sha: source_sha, target_branch: 'this-isnt-real') }
it 'throws an ArgumentError' do
expect { subject }.to raise_error(ArgumentError)
@@ -2187,8 +2204,9 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
it "calls Gitaly's OperationService" do
expect_any_instance_of(Gitlab::GitalyClient::OperationService)
- .to receive(:user_ff_branch).with(user, source_sha, target_branch)
- .and_return(nil)
+ .to receive(:user_ff_branch).with(
+ user, source_sha: source_sha, target_branch: target_branch, target_sha: branch_head
+ ).and_return(nil)
subject
end
diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
index 443efd9828f..84672eb81c0 100644
--- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::GitalyClient::OperationService do
+RSpec.describe Gitlab::GitalyClient::OperationService, feature_category: :source_code_management do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
@@ -279,12 +279,39 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
describe '#user_merge_branch' do
let(:target_branch) { 'master' }
+ let(:target_sha) { repository.commit(target_branch).sha }
let(:source_sha) { '5937ac0a7beb003549fc5fd26fc247adbce4a52e' }
let(:message) { 'Merge a branch' }
- subject { client.user_merge_branch(user, source_sha, target_branch, message) {} }
+ subject do
+ client.user_merge_branch(user,
+ source_sha: source_sha,
+ target_branch: target_branch,
+ target_sha: target_sha,
+ message: message
+ ) {}
+ end
+
+ it 'sends a user_merge_branch message', :freeze_time do
+ first_request =
+ Gitaly::UserMergeBranchRequest.new(
+ repository: repository.gitaly_repository,
+ user: gitaly_user,
+ commit_id: source_sha,
+ branch: target_branch,
+ expected_old_oid: target_sha,
+ message: message,
+ timestamp: Google::Protobuf::Timestamp.new(seconds: Time.now.utc.to_i)
+ )
+
+ second_request = Gitaly::UserMergeBranchRequest.new(apply: true)
+
+ expect_next_instance_of(Gitlab::GitalyClient::QueueEnumerator) do |instance|
+ expect(instance).to receive(:push).with(first_request).and_call_original
+ expect(instance).to receive(:push).with(second_request).and_call_original
+ expect(instance).to receive(:close)
+ end
- it 'sends a user_merge_branch message' do
expect(subject).to be_a(Gitlab::Git::OperationService::BranchUpdate)
expect(subject.newrev).to be_present
expect(subject.repo_created).to be(false)
@@ -431,12 +458,14 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
describe '#user_ff_branch' do
let(:target_branch) { 'my-branch' }
+ let(:target_sha) { '6d394385cf567f80a8fd85055db1ab4c5295806f' }
let(:source_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' }
let(:request) do
Gitaly::UserFFBranchRequest.new(
repository: repository.gitaly_repository,
branch: target_branch,
commit_id: source_sha,
+ expected_old_oid: target_sha,
user: gitaly_user
)
end
@@ -457,7 +486,13 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
.and_return(response)
end
- subject { client.user_ff_branch(user, source_sha, target_branch) }
+ subject do
+ client.user_ff_branch(user,
+ source_sha: source_sha,
+ target_branch: target_branch,
+ target_sha: target_sha
+ )
+ end
it 'sends a user_ff_branch message and returns a BranchUpdate object' do
expect(subject).to be_a(Gitlab::Git::OperationService::BranchUpdate)
diff --git a/spec/lib/gitlab/redis/cache_spec.rb b/spec/lib/gitlab/redis/cache_spec.rb
index 82ff8a26199..64615c4d9ad 100644
--- a/spec/lib/gitlab/redis/cache_spec.rb
+++ b/spec/lib/gitlab/redis/cache_spec.rb
@@ -26,5 +26,22 @@ RSpec.describe Gitlab::Redis::Cache do
expect(described_class.active_support_config[:expires_in]).to eq(1.day)
end
+
+ context 'when encountering an error' do
+ let(:cache) { ActiveSupport::Cache::RedisCacheStore.new(**described_class.active_support_config) }
+
+ subject { cache.read('x') }
+
+ before do
+ described_class.with do |redis|
+ allow(redis).to receive(:get).and_raise(::Redis::CommandError)
+ end
+ end
+
+ it 'logs error' do
+ expect(::Gitlab::ErrorTracking).to receive(:log_exception)
+ subject
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/redis/rate_limiting_spec.rb b/spec/lib/gitlab/redis/rate_limiting_spec.rb
index e79c070df93..d82228426f0 100644
--- a/spec/lib/gitlab/redis/rate_limiting_spec.rb
+++ b/spec/lib/gitlab/redis/rate_limiting_spec.rb
@@ -4,4 +4,21 @@ require 'spec_helper'
RSpec.describe Gitlab::Redis::RateLimiting do
include_examples "redis_new_instance_shared_examples", 'rate_limiting', Gitlab::Redis::Cache
+
+ describe '.cache_store' do
+ context 'when encountering an error' do
+ subject { described_class.cache_store.read('x') }
+
+ before do
+ described_class.with do |redis|
+ allow(redis).to receive(:get).and_raise(::Redis::CommandError)
+ end
+ end
+
+ it 'logs error' do
+ expect(::Gitlab::ErrorTracking).to receive(:log_exception)
+ subject
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/redis/repository_cache_spec.rb b/spec/lib/gitlab/redis/repository_cache_spec.rb
index 8cdc4580f9e..2c167a6eb62 100644
--- a/spec/lib/gitlab/redis/repository_cache_spec.rb
+++ b/spec/lib/gitlab/redis/repository_cache_spec.rb
@@ -17,5 +17,20 @@ RSpec.describe Gitlab::Redis::RepositoryCache, feature_category: :scalability do
it 'has a default ttl of 8 hours' do
expect(described_class.cache_store.options[:expires_in]).to eq(8.hours)
end
+
+ context 'when encountering an error' do
+ subject { described_class.cache_store.read('x') }
+
+ before do
+ described_class.with do |redis|
+ allow(redis).to receive(:get).and_raise(::Redis::CommandError)
+ end
+ end
+
+ it 'logs error' do
+ expect(::Gitlab::ErrorTracking).to receive(:log_exception)
+ subject
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
index 7d36e67ddbf..15df2cea909 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::SlashCommands::Presenters::IssueMove do
let_it_be(:other_project) { create(:project) }
let_it_be(:old_issue, reload: true) { create(:issue, project: project) }
- let(:new_issue) { Issues::MoveService.new(project: project, current_user: user).execute(old_issue, other_project) }
+ let(:new_issue) { Issues::MoveService.new(container: project, current_user: user).execute(old_issue, other_project) }
let(:attachment) { subject[:attachments].first }
subject { described_class.new(new_issue).present(old_issue) }
diff --git a/spec/migrations/recount_epic_cache_counts_v3_spec.rb b/spec/migrations/recount_epic_cache_counts_v3_spec.rb
new file mode 100644
index 00000000000..24b89ab30ca
--- /dev/null
+++ b/spec/migrations/recount_epic_cache_counts_v3_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe RecountEpicCacheCountsV3, :migration, feature_category: :portfolio_management do
+ let(:migration) { described_class::MIGRATION }
+
+ describe '#up' do
+ it 'schedules a batched background migration' do
+ migrate!
+
+ expect(migration).to have_scheduled_batched_migration(
+ table_name: :epics,
+ column_name: :id,
+ interval: described_class::DELAY_INTERVAL,
+ batch_size: described_class::BATCH_SIZE,
+ max_batch_size: described_class::MAX_BATCH_SIZE,
+ sub_batch_size: described_class::SUB_BATCH_SIZE
+ )
+ end
+ end
+
+ describe '#down' do
+ it 'deletes all batched migration records' do
+ migrate!
+ schema_migrate_down!
+
+ expect(migration).not_to have_scheduled_batched_migration
+ end
+ end
+end
diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb
index 9a78d7b1719..c3484c4a42c 100644
--- a/spec/models/hooks/project_hook_spec.rb
+++ b/spec/models/hooks/project_hook_spec.rb
@@ -79,17 +79,91 @@ RSpec.describe ProjectHook, feature_category: :integrations do
end
describe '#update_last_failure', :clean_gitlab_redis_shared_state do
- let(:hook) { build(:project_hook) }
+ let_it_be(:hook) { create(:project_hook) }
+
+ def last_failure
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.get(hook.project.last_failure_redis_key)
+ end
+ end
+
+ def any_failed?
+ Gitlab::Redis::SharedState.with do |redis|
+ Gitlab::Utils.to_boolean(redis.get(hook.project.web_hook_failure_redis_key))
+ end
+ end
it 'is a method of this class' do
expect { hook.update_last_failure }.not_to raise_error
end
context 'when the hook is executable' do
- it 'does not update the state' do
- expect(Gitlab::Redis::SharedState).not_to receive(:with)
+ let(:redis_key) { hook.project.web_hook_failure_redis_key }
+
+ def redis_value
+ any_failed?
+ end
+
+ context 'when the state was previously failing' do
+ before do
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set(redis_key, true)
+ end
+ end
- hook.update_last_failure
+ it 'does update the state' do
+ expect { hook.update_last_failure }.to change { redis_value }.to(false)
+ end
+
+ context 'when there is another failing sibling hook' do
+ before do
+ create(:project_hook, :permanently_disabled, project: hook.project)
+ end
+
+ it 'does not update the state' do
+ expect { hook.update_last_failure }.not_to change { redis_value }.from(true)
+ end
+
+ it 'caches the current value' do
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis).to receive(:set).with(redis_key, 'true', ex: 1.hour).and_call_original
+ end
+
+ hook.update_last_failure
+ end
+ end
+ end
+
+ context 'when the state was previously unknown' do
+ before do
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.del(redis_key)
+ end
+ end
+
+ it 'does not update the state' do
+ expect { hook.update_last_failure }.not_to change { redis_value }.from(nil)
+ end
+ end
+
+ context 'when the state was previously not failing' do
+ before do
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set(redis_key, false)
+ end
+ end
+
+ it 'does not update the state' do
+ expect { hook.update_last_failure }.not_to change { redis_value }.from(false)
+ end
+
+ it 'does not cache the current value' do
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis).not_to receive(:set)
+ end
+
+ hook.update_last_failure
+ end
end
end
@@ -98,28 +172,34 @@ RSpec.describe ProjectHook, feature_category: :integrations do
allow(hook).to receive(:executable?).and_return(false)
end
- def last_failure
- Gitlab::Redis::SharedState.with do |redis|
- redis.get("web_hooks:last_failure:project-#{hook.project.id}")
- end
- end
-
context 'there is no prior value', :freeze_time do
- it 'updates the state' do
+ it 'updates last_failure' do
expect { hook.update_last_failure }.to change { last_failure }.to(Time.current)
end
+
+ it 'updates any_failed?' do
+ expect { hook.update_last_failure }.to change { any_failed? }.to(true)
+ end
end
- context 'there is a prior value, from before now' do
+ context 'when there is a prior last_failure, from before now' do
it 'updates the state' do
the_future = 1.minute.from_now
-
hook.update_last_failure
travel_to(the_future) do
expect { hook.update_last_failure }.to change { last_failure }.to(the_future.iso8601)
end
end
+
+ it 'does not change the failing state' do
+ the_future = 1.minute.from_now
+ hook.update_last_failure
+
+ travel_to(the_future) do
+ expect { hook.update_last_failure }.not_to change { any_failed? }.from(true)
+ end
+ end
end
context 'there is a prior value, from after now' do
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 4bfa3328037..72958a54e10 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -443,7 +443,7 @@ RSpec.describe WebHook, feature_category: :integrations do
describe '#update_last_failure' do
it 'is a method of this class' do
- expect { described_class.new.update_last_failure }.not_to raise_error
+ expect { described_class.new(project: project).update_last_failure }.not_to raise_error
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index c0af71a7e27..23dae7d0374 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -8826,6 +8826,14 @@ RSpec.describe Project, factory_default: :keep, feature_category: :projects do
end
end
+ it_behaves_like 'something that has web-hooks' do
+ let_it_be_with_reload(:object) { create(:project) }
+
+ def create_hook
+ create(:project_hook, project: object)
+ end
+ end
+
private
def finish_job(export_job)
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index f3a8a9cded3..b8780b3faae 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1848,6 +1848,8 @@ RSpec.describe Repository, feature_category: :source_code_management do
end
describe '#expire_root_ref_cache' do
+ let(:project) { create(:project) }
+
it 'expires the root reference cache' do
repository.root_ref
@@ -1949,6 +1951,40 @@ RSpec.describe Repository, feature_category: :source_code_management do
end
end
+ describe '#merge_to_branch' do
+ let(:merge_request) do
+ create(:merge_request, source_branch: 'feature', target_branch: project.default_branch, source_project: project)
+ end
+
+ it 'merges two branches and returns the merge commit id' do
+ message = 'New merge commit'
+ merge_commit_id =
+ repository.merge_to_branch(user,
+ source_sha: merge_request.diff_head_sha,
+ target_branch: merge_request.target_branch,
+ target_sha: repository.commit(merge_request.target_branch).sha,
+ message: message)
+
+ expect(repository.commit(merge_commit_id).message).to eq(message)
+ expect(repository.commit(merge_request.target_branch).sha).to eq(merge_commit_id)
+ end
+
+ it 'does not merge if target branch has been changed' do
+ target_sha = project.commit.sha
+
+ repository.create_file(user, 'file.txt', 'CONTENT', message: 'Add file', branch_name: project.default_branch)
+
+ merge_commit_id =
+ repository.merge_to_branch(user,
+ source_sha: merge_request.diff_head_sha,
+ target_branch: merge_request.target_branch,
+ target_sha: target_sha,
+ message: 'New merge commit')
+
+ expect(merge_commit_id).to be_nil
+ end
+ end
+
describe '#merge_to_ref' do
let(:merge_request) do
create(:merge_request, source_branch: 'feature',
@@ -1975,15 +2011,20 @@ RSpec.describe Repository, feature_category: :source_code_management do
end
describe '#ff_merge' do
+ let(:target_branch) { 'ff-target' }
+ let(:merge_request) do
+ create(:merge_request, source_branch: 'feature', target_branch: target_branch, source_project: project)
+ end
+
before do
- repository.add_branch(user, 'ff-target', 'feature~5')
+ repository.add_branch(user, target_branch, 'feature~5')
end
it 'merges the code and return the commit id' do
- merge_request = create(:merge_request, source_branch: 'feature', target_branch: 'ff-target', source_project: project)
merge_commit_id = repository.ff_merge(user,
merge_request.diff_head_sha,
merge_request.target_branch,
+ target_sha: repository.commit(merge_request.target_branch).sha,
merge_request: merge_request)
merge_commit = repository.commit(merge_commit_id)
@@ -1992,14 +2033,24 @@ RSpec.describe Repository, feature_category: :source_code_management do
end
it 'sets the `in_progress_merge_commit_sha` flag for the given merge request' do
- merge_request = create(:merge_request, source_branch: 'feature', target_branch: 'ff-target', source_project: project)
merge_commit_id = repository.ff_merge(user,
merge_request.diff_head_sha,
merge_request.target_branch,
+ target_sha: repository.commit(merge_request.target_branch).sha,
merge_request: merge_request)
expect(merge_request.in_progress_merge_commit_sha).to eq(merge_commit_id)
end
+
+ it 'does not merge if target branch has been changed' do
+ target_sha = project.commit(target_branch).sha
+
+ repository.create_file(user, 'file.txt', 'CONTENT', message: 'Add file', branch_name: target_branch)
+
+ merge_commit_id = repository.ff_merge(user, merge_request.diff_head_sha, target_branch, target_sha: target_sha)
+
+ expect(merge_commit_id).to be_nil
+ end
end
describe '#rebase' do
diff --git a/spec/requests/api/graphql/issue/issue_spec.rb b/spec/requests/api/graphql/issue/issue_spec.rb
index 101de692aa5..3665fbc2df8 100644
--- a/spec/requests/api/graphql/issue/issue_spec.rb
+++ b/spec/requests/api/graphql/issue/issue_spec.rb
@@ -154,6 +154,47 @@ RSpec.describe 'Query.issue(id)', feature_category: :team_planning do
end
end
+ context 'when selecting `related_merge_requests`' do
+ let(:issue_fields) { ['relatedMergeRequests { nodes { id } }'] }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:mr_project) { project }
+ let!(:merge_request) do
+ attributes = {
+ author: user,
+ source_project: mr_project,
+ target_project: mr_project,
+ source_branch: 'master',
+ target_branch: 'test',
+ description: "See #{issue.to_reference}"
+ }
+
+ create(:merge_request, attributes).tap do |merge_request|
+ create(:note, :system, project: issue.project, noteable: issue,
+ author: user, note: merge_request.to_reference(full: true))
+ end
+ end
+
+ before do
+ project.add_developer(current_user)
+
+ post_graphql(query, current_user: current_user)
+ end
+
+ it 'returns the related merge request' do
+ expect(issue_data['relatedMergeRequests']['nodes']).to include a_hash_including({
+ 'id' => merge_request.to_global_id.to_s
+ })
+ end
+
+ context 'no permission to related merge request' do
+ let_it_be(:mr_project) { create(:project, :private) }
+
+ it 'does not return the related merge request' do
+ expect(issue_data['relatedMergeRequests']['nodes']).to be_empty
+ end
+ end
+ end
+
context 'when there is a confidential issue' do
let!(:confidential_issue) do
create(:issue, :confidential, project: project)
diff --git a/spec/requests/api/graphql/project/alert_management/alerts_spec.rb b/spec/requests/api/graphql/project/alert_management/alerts_spec.rb
index 304edfbf4e4..55d223daf27 100644
--- a/spec/requests/api/graphql/project/alert_management/alerts_spec.rb
+++ b/spec/requests/api/graphql/project/alert_management/alerts_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe 'getting Alert Management Alerts', feature_category: :incident_ma
let(:fields) do
<<~QUERY
nodes {
- #{all_graphql_fields_for('AlertManagementAlert', excluded: ['assignees'])}
+ #{all_graphql_fields_for('AlertManagementAlert', excluded: %w[assignees relatedMergeRequests])}
}
QUERY
end
diff --git a/spec/serializers/issue_entity_spec.rb b/spec/serializers/issue_entity_spec.rb
index 4bb1e00fa18..cc3635a0afc 100644
--- a/spec/serializers/issue_entity_spec.rb
+++ b/spec/serializers/issue_entity_spec.rb
@@ -65,7 +65,7 @@ RSpec.describe IssueEntity do
before do
project.add_developer(member)
public_project.add_developer(member)
- Issues::MoveService.new(project: public_project, current_user: member).execute(issue, project)
+ Issues::MoveService.new(container: public_project, current_user: member).execute(issue, project)
end
context 'when user cannot read target project' do
diff --git a/spec/serializers/pipeline_details_entity_spec.rb b/spec/serializers/pipeline_details_entity_spec.rb
index 52a01787711..de05d2afd2b 100644
--- a/spec/serializers/pipeline_details_entity_spec.rb
+++ b/spec/serializers/pipeline_details_entity_spec.rb
@@ -182,6 +182,7 @@ RSpec.describe PipelineDetailsEntity do
expect(source_jobs[cross_project_pipeline.id][:name]).to eq('cross-project')
expect(source_jobs[child_pipeline.id][:name]).to eq('child')
+ expect(source_jobs[child_pipeline.id][:retried]).to eq false
end
end
end
diff --git a/spec/services/issuable/destroy_service_spec.rb b/spec/services/issuable/destroy_service_spec.rb
index c72d48d5b77..29f548e1c47 100644
--- a/spec/services/issuable/destroy_service_spec.rb
+++ b/spec/services/issuable/destroy_service_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Issuable::DestroyService do
let(:group) { create(:group, :public) }
let(:project) { create(:project, :public, group: group) }
- subject(:service) { described_class.new(project: project, current_user: user) }
+ subject(:service) { described_class.new(container: project, current_user: user) }
describe '#execute' do
context 'when issuable is an issue' do
diff --git a/spec/services/issues/clone_service_spec.rb b/spec/services/issues/clone_service_spec.rb
index 67f32b85350..eafaea93015 100644
--- a/spec/services/issues/clone_service_spec.rb
+++ b/spec/services/issues/clone_service_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe Issues::CloneService do
let(:with_notes) { false }
subject(:clone_service) do
- described_class.new(project: old_project, current_user: user)
+ described_class.new(container: old_project, current_user: user)
end
shared_context 'user can clone issue' do
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index 324b2aa9fe2..11f4d6a9ed4 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe Issues::MoveService do
end
subject(:move_service) do
- described_class.new(project: old_project, current_user: user)
+ described_class.new(container: old_project, current_user: user)
end
shared_context 'user can move issue' do
diff --git a/spec/services/tasks_to_be_done/base_service_spec.rb b/spec/services/tasks_to_be_done/base_service_spec.rb
index bf6be6d46e5..7219dae60be 100644
--- a/spec/services/tasks_to_be_done/base_service_spec.rb
+++ b/spec/services/tasks_to_be_done/base_service_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe TasksToBeDone::BaseService do
subject(:service) do
TasksToBeDone::CreateCiTaskService.new(
- project: project,
+ container: project,
current_user: current_user,
assignee_ids: assignee_ids
)
diff --git a/spec/services/work_items/delete_service_spec.rb b/spec/services/work_items/delete_service_spec.rb
index 6cca5018852..69ae881a12f 100644
--- a/spec/services/work_items/delete_service_spec.rb
+++ b/spec/services/work_items/delete_service_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe WorkItems::DeleteService do
end
describe '#execute' do
- subject(:result) { described_class.new(project: project, current_user: user).execute(work_item) }
+ subject(:result) { described_class.new(container: project, current_user: user).execute(work_item) }
context 'when user can delete the work item' do
it { is_expected.to be_success }
diff --git a/spec/support/shared_examples/models/concerns/web_hooks/has_web_hooks_shared_examples.rb b/spec/support/shared_examples/models/concerns/web_hooks/has_web_hooks_shared_examples.rb
new file mode 100644
index 00000000000..cd6eb8c77fa
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/web_hooks/has_web_hooks_shared_examples.rb
@@ -0,0 +1,107 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'something that has web-hooks' do
+ describe '#any_hook_failed?', :clean_gitlab_redis_shared_state do
+ subject { object.any_hook_failed? }
+
+ context 'when there are no hooks' do
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when there are hooks' do
+ before do
+ create_hook
+ create_hook
+ end
+
+ it { is_expected.to eq(false) }
+
+ context 'when there is a failed hook' do
+ before do
+ hook = create_hook
+ hook.update!(recent_failures: WebHook::FAILURE_THRESHOLD + 1)
+ end
+
+ it { is_expected.to eq(true) }
+ end
+ end
+ end
+
+ describe '#cache_web_hook_failure', :clean_gitlab_redis_shared_state do
+ context 'when no value is passed' do
+ it 'stores the return value of #any_hook_failed? and passes it back' do
+ allow(object).to receive(:any_hook_failed?).and_return(true)
+
+ Gitlab::Redis::SharedState.with do |r|
+ expect(r).to receive(:set)
+ .with(object.web_hook_failure_redis_key, 'true', ex: 1.hour)
+ .and_call_original
+ end
+
+ expect(object.cache_web_hook_failure).to eq(true)
+ end
+ end
+
+ context 'when a value is passed' do
+ it 'stores the value and passes it back' do
+ expect(object).not_to receive(:any_hook_failed?)
+
+ Gitlab::Redis::SharedState.with do |r|
+ expect(r).to receive(:set)
+ .with(object.web_hook_failure_redis_key, 'foo', ex: 1.hour)
+ .and_call_original
+ end
+
+ expect(object.cache_web_hook_failure(:foo)).to eq(:foo)
+ end
+ end
+ end
+
+ describe '#get_web_hook_failure', :clean_gitlab_redis_shared_state do
+ subject { object.get_web_hook_failure }
+
+ context 'when no value is stored' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'when stored as true' do
+ before do
+ object.cache_web_hook_failure(true)
+ end
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when stored as false' do
+ before do
+ object.cache_web_hook_failure(false)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
+ describe '#fetch_web_hook_failure', :clean_gitlab_redis_shared_state do
+ context 'when a value has not been stored' do
+ it 'does not call #any_hook_failed?' do
+ expect(object.get_web_hook_failure).to be_nil
+ expect(object).to receive(:any_hook_failed?).and_return(true)
+
+ expect(object.fetch_web_hook_failure).to eq(true)
+ expect(object.get_web_hook_failure).to eq(true)
+ end
+ end
+
+ context 'when a value has been stored' do
+ before do
+ object.cache_web_hook_failure(true)
+ end
+
+ it 'does not call #any_hook_failed?' do
+ expect(object).not_to receive(:any_hook_failed?)
+
+ expect(object.fetch_web_hook_failure).to eq(true)
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb
index d4af9e570d1..6c8b792bf92 100644
--- a/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb
@@ -5,7 +5,7 @@ RSpec.shared_examples 'graphql issue list request spec' do
let(:fields) do
<<~QUERY
nodes {
- #{all_graphql_fields_for('issues'.classify)}
+ #{all_graphql_fields_for('issues'.classify, excluded: ['relatedMergeRequests'])}
}
QUERY
end
@@ -683,6 +683,28 @@ RSpec.shared_examples 'graphql issue list request spec' do
end
end
+ context 'when selecting `related_merge_requests`' do
+ let(:fields) do
+ <<~QUERY
+ nodes {
+ relatedMergeRequests {
+ nodes {
+ id
+ }
+ }
+ }
+ QUERY
+ end
+
+ it 'limits the field to 1 execution' do
+ post_query
+
+ expect_graphql_errors_to_include(
+ '"relatedMergeRequests" field can be requested only for 1 Issue(s) at a time.'
+ )
+ end
+ end
+
it 'includes a web_url' do
post_query
diff --git a/spec/workers/tasks_to_be_done/create_worker_spec.rb b/spec/workers/tasks_to_be_done/create_worker_spec.rb
index e884a71933e..c3c3612f9a7 100644
--- a/spec/workers/tasks_to_be_done/create_worker_spec.rb
+++ b/spec/workers/tasks_to_be_done/create_worker_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe TasksToBeDone::CreateWorker do
expect(service_class)
.to receive(:new)
- .with(project: member_task.project, current_user: current_user, assignee_ids: assignee_ids)
+ .with(container: member_task.project, current_user: current_user, assignee_ids: assignee_ids)
.and_call_original
end