Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-07-03 18:07:27 +00:00
parent d00599dae8
commit a3796779c9
43 changed files with 871 additions and 230 deletions

View File

@ -81,7 +81,6 @@ export default {
'app/assets/javascripts/emoji/components/picker.vue',
'app/assets/javascripts/error_tracking/components/error_details.vue',
'app/assets/javascripts/error_tracking/components/error_tracking_list.vue',
'app/assets/javascripts/error_tracking_settings/components/project_dropdown.vue',
'app/assets/javascripts/feature_flags/components/empty_state.vue',
'app/assets/javascripts/feature_flags/components/environments_dropdown.vue',
'app/assets/javascripts/feature_flags/components/feature_flags.vue',

View File

@ -11,7 +11,7 @@ include:
inputs:
job_name: "e2e-test-report"
job_stage: "report"
report_title: "E2E Test Result Summary"
report_title: '<a href="https://handbook.gitlab.com/handbook/engineering/testing/end-to-end-pipeline-monitoring/#allure-report">E2E Test Result Summary</a>'
aws_access_key_id_variable_name: "QA_ALLURE_AWS_ACCESS_KEY_ID"
aws_secret_access_key_variable_name: "QA_ALLURE_AWS_SECRET_ACCESS_KEY"
gitlab_auth_token_variable_name: "PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE"

View File

@ -1225,9 +1225,8 @@
- <<: *if-schedule-maintenance
- <<: *if-dot-com-gitlab-org-default-branch
changes: *assets-compilation-patterns
# push assets for stable branches (canonical & security)
- <<: *if-tag
- <<: *if-sync-changes-on-stable-branches
changes: *assets-compilation-patterns
- <<: *if-dot-com-gitlab-org-merge-request
changes:
- ".gitlab/ci/caching.gitlab-ci.yml"
@ -1238,6 +1237,7 @@
- if: '$ENABLE_CACHE_ASSETS == "true"'
when: manual
allow_failure: true
- !reference [".releases:rules:canonical-dot-com-security-gitlab-stable-branch-only", rules]
.caching:rules:packages-cleanup:
rules:

View File

@ -1,13 +1,12 @@
<script>
import { GlCard, GlLoadingIcon } from '@gitlab/ui';
// eslint-disable-next-line no-restricted-imports
import { mapState, mapGetters, mapActions } from 'vuex';
import CrudComponent from '~/vue_shared/components/crud_component.vue';
import statisticsLabels from '../constants';
export default {
components: {
GlCard,
GlLoadingIcon,
CrudComponent,
},
data() {
return {
@ -28,24 +27,18 @@ export default {
</script>
<template>
<gl-card class="gl-h-full" body-class="gl-h-full gl-py-0">
<template #header>
<h3 class="gl-m-0 gl-inline-flex gl-items-center gl-gap-2 gl-self-center gl-text-base">
{{ __('Statistics') }}
</h3>
</template>
<template #default>
<gl-loading-icon v-if="isLoading" size="md" class="my-3" />
<template v-else>
<p
v-for="statistic in getStatistics(statisticsLabels)"
:key="statistic.key"
:class="['js-stats', 'gl-py-4', 'gl-m-0', 'gl-border-b', 'last:gl-border-b-0']"
>
{{ statistic.label }}
<span class="light gl-float-right">{{ statistic.value }}</span>
</p>
</template>
</template>
</gl-card>
<crud-component
:is-loading="isLoading"
:title="__('Statistics')"
:body-class="{ '!gl-mt-0': !isLoading }"
>
<p
v-for="statistic in getStatistics(statisticsLabels)"
:key="statistic.key"
:class="['js-stats', 'gl-py-4', 'gl-m-0', 'gl-border-b', 'last:gl-border-b-0']"
>
{{ statistic.label }}
<span class="light gl-float-right">{{ statistic.value }}</span>
</p>
</crud-component>
</template>

View File

@ -87,14 +87,7 @@ export default {
'isProjectInvalid',
'projectSelectionLabel',
]),
...mapState([
'enabled',
'integrated',
'projects',
'selectedProject',
'settingsLoading',
'token',
]),
...mapState(['enabled', 'integrated', 'projects', 'selectedProject', 'settingsLoading']),
showGitlabDsnSetting() {
return this.integrated && this.enabled && this.gitlabDsn;
},
@ -218,7 +211,6 @@ export default {
:project-selection-label="projectSelectionLabel"
:projects="projects"
:selected-project="selectedProject"
:token="token"
@select-project="updateSelectedProject"
/>
</div>

View File

@ -36,10 +36,6 @@ export default {
type: String,
required: true,
},
token: {
type: String,
required: true,
},
},
computed: {
listboxItems() {

View File

@ -86,7 +86,8 @@ export default {
<work-item-attribute
v-if="datesText"
anchor-id="issuable-due-date"
wrapper-component-class="issuable-due-date"
wrapper-component="button"
wrapper-component-class="issuable-due-date !gl-cursor-help gl-text-subtle gl-bg-transparent gl-border-0 gl-p-0 focus-visible:gl-focus-inset"
:title="datesText"
title-component-class="gl-mr-3"
:tooltip-text="datesTooltipTitle"

View File

@ -68,12 +68,13 @@ export default {
</script>
<template>
<component :is="isLink ? 'gl-link' : 'span'" :href="href">
<component :is="isLink ? 'gl-link' : 'span'" :href="isLink ? href : null">
<!-- wrapper -->
<component
:is="wrapperComponent"
ref="wrapperRef"
:class="wrapperComponentClass"
:href="!isLink ? href : null"
:data-testid="anchorId"
>
<!-- icon -->

View File

@ -68,11 +68,11 @@ export default {
<work-item-attribute
anchor-id="issuable-milestone"
:title="milestone.title"
wrapper-component-class="gl-text-sm !gl-text-subtle"
wrapper-component="a"
wrapper-component-class="!gl-text-subtle !gl-cursor-help gl-bg-transparent gl-border-0 gl-p-0 focus-visible:gl-focus-inset"
:tooltip-text="milestoneDate"
tooltip-placement="top"
class="issuable-milestone gl-mr-3"
is-link
:href="milestoneLink"
>
<template #icon>

View File

@ -13,6 +13,9 @@ import WorkItemBulkEditAssignee from './work_item_bulk_edit_assignee.vue';
import WorkItemBulkEditDropdown from './work_item_bulk_edit_dropdown.vue';
import WorkItemBulkEditLabels from './work_item_bulk_edit_labels.vue';
const WorkItemBulkEditIteration = () =>
import('ee_component/work_items/components/list/work_item_bulk_edit_iteration.vue');
export default {
name: 'WorkItemBulkEditSidebar',
confidentialityItems: [
@ -37,9 +40,10 @@ export default {
WorkItemBulkEditAssignee,
WorkItemBulkEditDropdown,
WorkItemBulkEditLabels,
WorkItemBulkEditIteration,
},
mixins: [glFeatureFlagsMixin()],
inject: ['hasIssuableHealthStatusFeature'],
inject: ['hasIssuableHealthStatusFeature', 'hasIterationsFeature'],
props: {
checkedItems: {
type: Array,
@ -70,6 +74,7 @@ export default {
removeLabelIds: [],
state: undefined,
subscription: undefined,
iteration: undefined,
};
},
apollo: {
@ -155,6 +160,7 @@ export default {
healthStatus: camelCase(this.healthStatus),
}
: undefined,
iterationWidget: this.iteration ? { iterationId: this.iteration } : undefined,
},
},
});
@ -239,5 +245,11 @@ export default {
:label="__('Confidentiality')"
data-testid="bulk-edit-confidentiality"
/>
<work-item-bulk-edit-iteration
v-if="shouldUseGraphQLBulkEdit && !isEpicsList && hasIterationsFeature"
v-model="iteration"
:full-path="fullPath"
:is-group="isGroup"
/>
</gl-form>
</template>

View File

@ -256,39 +256,48 @@ export default {
await this.updateLabels({ addLabelIds, removeLabelIds });
}
},
updateDraftCache() {
updateDraftCache(removeLabelIds = []) {
let labels = this.labelsCache.filter(({ id }) => this.selectedLabelsIds.includes(id));
if (removeLabelIds.length) {
labels = labels.filter(({ id }) => !removeLabelIds.includes(id));
}
this.$emit('updateWidgetDraft', {
workItemType: this.workItemType,
fullPath: this.fullPath,
labels: this.labelsCache.filter(({ id }) => this.selectedLabelsIds.includes(id)),
labels,
});
},
async updateLabels({ addLabelIds = [], removeLabelIds = [] }) {
try {
this.updateInProgress = true;
const {
data: {
workItemUpdate: { errors },
},
} = await this.$apollo.mutate({
mutation: updateWorkItemMutation,
variables: {
input: {
id: this.workItemId,
labelsWidget: {
addLabelIds,
removeLabelIds,
if (this.isCreateFlow) {
this.updateDraftCache(removeLabelIds);
} else {
const {
data: {
workItemUpdate: { errors },
},
} = await this.$apollo.mutate({
mutation: updateWorkItemMutation,
variables: {
input: {
id: this.workItemId,
labelsWidget: {
addLabelIds,
removeLabelIds,
},
},
},
},
});
});
if (errors.length > 0) {
throw new Error();
if (errors.length > 0) {
throw new Error();
}
this.track('updated_labels');
}
this.track('updated_labels');
this.$emit('labelsUpdated', [...addLabelIds, ...removeLabelIds]);
} catch {
this.$emit('error', i18n.updateError);

View File

@ -498,7 +498,7 @@ module ProjectsHelper
return unless current_user
return if project.empty_repo?
if current_user.already_forked?(project) && !current_user.has_forkable_groups?
if current_user.already_forked?(project) && !current_user.has_groups_allowing_project_creation?
user_fork_url = namespace_project_path(current_user, current_user.fork_of(project))
end

View File

@ -1572,7 +1572,7 @@ class User < ApplicationRecord
end
def can_select_namespace?
several_namespaces? || admin
has_groups_allowing_project_creation? || admin
end
def can?(action, subject = :global, **opts)
@ -1617,15 +1617,6 @@ class User < ApplicationRecord
end
# rubocop: enable CodeReuse/ServiceClass
def several_namespaces?
union_sql = ::Gitlab::SQL::Union.new(
[owned_groups,
maintainers_groups,
groups_with_developer_project_access]).to_sql
::Group.from("(#{union_sql}) #{::Group.table_name}").any?
end
def namespace_id
namespace.try :id
end
@ -2011,14 +2002,13 @@ class User < ApplicationRecord
enabled_following && user.enabled_following
end
def has_forkable_groups?
Groups::AcceptingProjectCreationsFinder.new(self).execute.exists?
def has_groups_allowing_project_creation?
groups_allowing_project_creation.exists?
end
def forkable_namespaces
strong_memoize(:forkable_namespaces) do
personal_namespace = Namespace.where(id: namespace_id)
groups_allowing_project_creation = Groups::AcceptingProjectCreationsFinder.new(self).execute
Namespace.from_union(
[
@ -2899,9 +2889,7 @@ class User < ApplicationRecord
def ci_owned_project_runners_from_project_members
project_ids = ci_project_ids_for_project_members(Gitlab::Access::MAINTAINER)
Ci::Runner
.joins(:runner_projects)
.where(runner_projects: { project: project_ids })
Ci::Runner.belonging_to_project(project_ids)
end
def ci_owned_project_runners_from_group_members
@ -2989,6 +2977,10 @@ class User < ApplicationRecord
errors.add(:username, _('has already been taken'))
end
end
def groups_allowing_project_creation
Groups::AcceptingProjectCreationsFinder.new(self).execute
end
end
User.prepend_mod_with('User')

View File

@ -30,78 +30,59 @@
.admin-dashboard.gl-mt-3{ data: { event_tracking_load: 'true', event_tracking: 'view_admin_dashboard_pageload' } }
%h2.gl-heading-2= _('Instance overview')
.gl-grid.md:gl-grid-cols-2.lg:gl-grid-cols-3.gl-gap-5
- component_params = { card_options: { class: 'gl-h-full' }, header_options: { class: 'gl-flex gl-justify-between gl-items-start' }, body_options: { class: 'gl-h-full gl-pt-0' } }
- component_params = { options: { class: 'gl-flex gl-flex-col gl-h-full' }, body_options: { class: 'gl-h-full gl-mt-0' } }
- if current_user.can_admin_all_resources?
= render Pajamas::CardComponent.new(**component_params) do |c|
- c.with_header do
%h3.gl-self-center.gl-text-base.gl-inline-flex.gl-gap-2.gl-items-center.gl-m-0
- approximate_projects_count = approximate_count_with_delimiters(@counts, Project)
= s_('AdminArea|Projects')
= render Pajamas::BadgeComponent.new(approximate_projects_count, variant: :muted, aria: { hidden: "true" })
= render ::Layouts::CrudComponent.new(s_('AdminArea|Projects'), count: approximate_count_with_delimiters(@counts, Project), **component_params) do |c|
- c.with_actions do
- if current_user.can_create_project?
= render Pajamas::ButtonComponent.new(href: new_project_path, size: :small) do
= s_('AdminArea|New project')
- c.with_body do
.gl-flex.gl-flex-col.gl-h-full
- @projects.each do |project|
.gl-flex.gl-py-4{ class: ('gl-border-b' if @projects.last != project) }
.gl-mr-auto
= link_to project.full_name, admin_project_path(project)
%span.gl-whitespace-nowrap.gl-text-right.gl-text-sm.gl-text-subtle
#{time_ago_with_tooltip(project.created_at)}
.gl-grow
.gl-pt-4
= render Pajamas::ButtonComponent.new(href: admin_projects_path(sort: 'created_desc'), block: true) do
= s_('AdminArea|View latest projects')
= render Pajamas::CardComponent.new(**component_params) do |c|
- c.with_header do
%h3.gl-self-center.gl-text-base.gl-inline-flex.gl-gap-2.gl-items-center.gl-m-0
= s_('AdminArea|Total Users')
= render Pajamas::BadgeComponent.new(approximate_count_with_delimiters(@counts, User), variant: :muted, aria: { hidden: "true" })
- @projects.each do |project|
.gl-flex.gl-py-4{ class: ('gl-border-b' if @projects.last != project) }
.gl-mr-auto
= link_to project.full_name, admin_project_path(project)
%span.gl-whitespace-nowrap.gl-text-right.gl-text-sm.gl-text-subtle
#{time_ago_with_tooltip(project.created_at)}
- c.with_footer do
= render Pajamas::ButtonComponent.new(href: admin_projects_path(sort: 'created_desc'), block: true) do
= s_('AdminArea|View latest projects')
= render ::Layouts::CrudComponent.new(s_('AdminArea|Total Users'), count: approximate_count_with_delimiters(@counts, User), **component_params) do |c|
- c.with_actions do
= render Pajamas::ButtonComponent.new(href: new_admin_user_path, size: :small) do
= s_('AdminArea|New user')
- c.with_body do
.gl-flex.gl-flex-col.gl-h-full
- @users.each do |user|
.gl-flex.gl-py-4{ class: ('gl-border-b' if @users.last != user) }
.gl-mr-auto
= link_to [:admin, user] do
= user.name
%span.gl-whitespace-nowrap.gl-text-right.gl-text-sm.gl-text-subtle
#{time_ago_with_tooltip(user.created_at)}
.gl-grow
.gl-flex.gl-pt-4.gl-gap-3
= render Pajamas::ButtonComponent.new(href: admin_users_path(sort: 'created_desc'), block: true) do
= s_('AdminArea|View latest users')
= render Pajamas::ButtonComponent.new(href: admin_dashboard_stats_path, block: true, button_options: { class: '!gl-mt-0'}) do
= s_('AdminArea|Users statistics')
= render Pajamas::CardComponent.new(**component_params) do |c|
- c.with_header do
%h3.gl-self-center.gl-text-base.gl-inline-flex.gl-gap-2.gl-items-center.gl-m-0
- approximate_groups_count = approximate_count_with_delimiters(@counts, Group)
= s_('AdminArea|Groups')
= render Pajamas::BadgeComponent.new(approximate_groups_count, variant: :muted, aria: { hidden: "true" })
- @users.each do |user|
.gl-flex.gl-py-4{ class: ('gl-border-b' if @users.last != user) }
.gl-mr-auto
= link_to [:admin, user] do
= user.name
%span.gl-whitespace-nowrap.gl-text-right.gl-text-sm.gl-text-subtle
#{time_ago_with_tooltip(user.created_at)}
- c.with_footer do
.gl-flex.gl-gap-3
= render Pajamas::ButtonComponent.new(href: admin_users_path(sort: 'created_desc'), block: true) do
= s_('AdminArea|View latest users')
= render Pajamas::ButtonComponent.new(href: admin_dashboard_stats_path, block: true, button_options: { class: '!gl-mt-0'}) do
= s_('AdminArea|Users statistics')
= render ::Layouts::CrudComponent.new(s_('AdminArea|Groups'), count: approximate_count_with_delimiters(@counts, Group), **component_params) do |c|
- c.with_actions do
= render Pajamas::ButtonComponent.new(href: new_admin_group_path, size: :small) do
= s_('AdminArea|New group')
- c.with_body do
.gl-flex.gl-flex-col.gl-h-full
- @groups.each do |group|
.gl-flex.gl-py-4{ class: ('gl-border-b' if @groups.last != group) }
.gl-mr-auto
= link_to [:admin, group] do
= group.full_name
%span.gl-whitespace-nowrap.gl-text-right.gl-text-sm.gl-text-subtle
#{time_ago_with_tooltip(group.created_at)}
.gl-grow
.gl-pt-4
= render Pajamas::ButtonComponent.new(href: admin_groups_path(sort: 'created_desc'), block: true) do
= s_('AdminArea|View latest groups')
- @groups.each do |group|
.gl-flex.gl-py-4{ class: ('gl-border-b' if @groups.last != group) }
.gl-mr-auto
= link_to [:admin, group] do
= group.full_name
%span.gl-whitespace-nowrap.gl-text-right.gl-text-sm.gl-text-subtle
#{time_ago_with_tooltip(group.created_at)}
- c.with_footer do
= render Pajamas::ButtonComponent.new(href: admin_groups_path(sort: 'created_desc'), block: true) do
= s_('AdminArea|View latest groups')
#js-admin-statistics-container
= render Pajamas::CardComponent.new(card_options: { class: 'gl-h-full' }, body_options: { class: 'gl-h-full gl-py-0' }) do |c|
- c.with_header do
%h3.gl-self-center.gl-text-base.gl-inline-flex.gl-gap-2.gl-items-center.gl-m-0
= s_('AdminArea|Features')
= render ::Layouts::CrudComponent.new(s_('AdminArea|Features'), options: { class: 'gl-h-full' }, body_options: { class: 'gl-h-full gl-py-0' }) do |c|
- c.with_body do
= feature_entry(_('Sign up'),
href: general_admin_application_settings_path(anchor: 'js-signup-settings'),
@ -137,10 +118,7 @@
href: admin_runners_path,
enabled: Gitlab.config.gitlab_ci.shared_runners_enabled,
last: true)
= render Pajamas::CardComponent.new(card_options: { class: 'gl-h-full' }, body_options: { class: 'gl-h-full gl-py-0' }) do |c|
- c.with_header do
%h3.gl-m-0.gl-inline-flex.gl-items-center.gl-gap-2.gl-self-center.gl-text-base
= s_('AdminArea|Components')
= render ::Layouts::CrudComponent.new(s_('AdminArea|Components'), options: { class: 'gl-h-full' }, body_options: { class: 'gl-h-full gl-py-0' }) do |c|
- c.with_body do
- if show_version_check?
.gl-min-h-9.gl-py-4.gl-border-b

View File

@ -0,0 +1,10 @@
---
name: merge_request_merged_with_policy_violations
description: A merge request merged with security policy violations
introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/work_items/549813
introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/195775
feature_category: security_policy_management
milestone: '18.2'
saved_to_database: true
streamed: true
scope: [Project]

View File

@ -2,7 +2,7 @@
migration_job_name: MigrateScimIdentities
description: Migrate Group scoped scim identities to new table
feature_category: system_access
introduced_by_url: 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/170565'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/170565
milestone: '17.8'
queued_migration_version: 20250106175246
finalized_by: # version of the migration that finalized this BBM
finalized_by: '20250609233305'

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class FinalizeHkMigrateScimIdentities < Gitlab::Database::Migration[2.3]
milestone '18.2'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'MigrateScimIdentities',
table_name: :scim_identities,
column_name: :id,
job_arguments: [],
finalize: true
)
end
def down; end
end

View File

@ -0,0 +1 @@
4b66de59dfc5c2cf10047a57a9c2d13e18af615638221cd76e5eb7a2ac633415

View File

@ -12899,9 +12899,9 @@ CREATE TABLE compliance_management_frameworks (
color text NOT NULL,
namespace_id bigint NOT NULL,
pipeline_configuration_full_path text,
source_id bigint,
created_at timestamp with time zone,
updated_at timestamp with time zone,
source_id bigint,
CONSTRAINT check_08cd34b2c2 CHECK ((char_length(color) <= 10)),
CONSTRAINT check_1617e0b87e CHECK ((char_length(description) <= 255)),
CONSTRAINT check_ab00bc2193 CHECK ((char_length(name) <= 255)),

View File

@ -29,6 +29,7 @@ The **Overview** page displays:
- Instance details
- Maintenance windows
- Hosted runners
- Customer communication
## Tenant overview
@ -79,3 +80,15 @@ To view the current NAT gateway IP addresses for your GitLab Dedicated instance:
1. Select your tenant.
1. Select the **Configuration** tab.
1. Under **Tenant Details**, find your **NAT gateways**.
## Customer communication
The **Customer communication** section shows the **Operational email addresses** configured for your GitLab Dedicated instance. These email addresses receive notifications about your instance, including:
- Emergency maintenance
- Incidents
- Other critical updates
You cannot turn off notifications for operational email addresses.
To update your customer communication information, [submit a support ticket](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=4414917877650).

View File

@ -225,6 +225,34 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" \
If successful, returns a [`204 No Content`](rest/troubleshooting.md#status-codes) status code.
### Delete cache entries for a virtual registry
{{< history >}}
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/538327) in GitLab 18.2 [with a flag](../administration/feature_flags/_index.md) named `maven_virtual_registry`. Disabled by default.
{{< /history >}}
Schedule all cache entries for deletion in all exclusive upstream registries for a Maven virtual registry. Cache entries are not scheduled for deletion for upstream registries that are associated with other virtual registries.
```plaintext
DELETE /virtual_registries/packages/maven/registries/:id/cache
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the Maven virtual registry. |
Example request:
```shell
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" \
--header "Accept: application/json" \
--url "https://gitlab.example.com/api/v4/virtual_registries/packages/maven/registries/1/cache"
```
If successful, returns a [`204 No Content`](rest/troubleshooting.md#status-codes) status code.
## Manage upstream registries
Use the following endpoints to configure and manage upstream Maven registries.
@ -579,7 +607,7 @@ curl --request DELETE \
If successful, returns a [`204 No Content`](rest/troubleshooting.md#status-codes) status code.
### Purge cache entries for an upstream registry
### Delete cache entries for an upstream registry
{{< history >}}
@ -587,7 +615,7 @@ If successful, returns a [`204 No Content`](rest/troubleshooting.md#status-codes
{{< /history >}}
Purges all cache entries for a specific upstream registry in a Maven virtual registry.
Schedules all cache entries for deletion for a specific upstream registry in a Maven virtual registry.
```plaintext
DELETE /virtual_registries/packages/maven/upstreams/:id/cache

View File

@ -68,6 +68,7 @@ You can find all those directories listed in the [Linux package configuration do
- [Review Runner security considerations and recommendations](https://docs.gitlab.com/runner/security/).
- [Review CI/CD variables security considerations](../ci/variables/_index.md#cicd-variable-security).
- [Review pipeline security for usage and protection of secrets in CI/CD Pipelines](../ci/pipelines/pipeline_security.md).
- [Instance-wide compliance and security policy management](compliance_security_policy_management.md).
### Patching

View File

@ -0,0 +1,167 @@
---
stage: Security Risk Management
group: Security Policies
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
description: Learn how to apply security policies and compliance frameworks across multiple groups and projects from a single, centralized location.
title: Instance-wide compliance and security policy management
---
{{< details >}}
- Tier: Ultimate
- Offering: GitLab Self-Managed
- Status: Beta
{{< /details >}}
{{< history >}}
- [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/15864) in GitLab 18.2 [with a feature flag](../administration/feature_flags/_index.md) named `security_policies_csp`. Disabled by default.
{{< /history >}}
{{< alert type="flag" >}}
The availability of this feature is controlled by a feature flag.
For more information, see the history.
This feature is subject to change and may not ready for production use.
{{< /alert >}}
To apply security policies across multiple groups and projects from a single and centralized location, instance administrators can designate a compliance and security policy (CSP) group. This allows the instance administrators to:
- Create and configure security policies that automatically apply across your instance.
- Create compliance frameworks to make them available for other top-level groups.
- Scope policies to apply to compliance frameworks, groups, projects, or your entire instance.
- View comprehensive policy coverage to understand which policies are active and where they're active.
- Maintain centralized control while allowing teams to create their own additional policies.
## Prerequisites
- GitLab Self-Managed.
- GitLab 18.2 or later.
- You must be instance administrator.
- You must have an existing top-level group to serve as the CSP group.
- To use the REST API (optional), you must have a token with administrator access.
## Set up instance-wide compliance and security policy management
To set up instance-wide compliance and security policy management, you designate a CSP group and then create policies and compliance frameworks in the group.
### Designate a CSP group
You can designate a CSP group using either the GitLab UI or the REST API.
#### Using the GitLab UI
1. Go to **Admin Area** > **Settings** > **Security and Compliance**.
1. In the **Designate CSP Group** section, select an existing top-level group from the dropdown list.
1. Select **Save changes**.
#### Using the REST API
You can also designate a CSP group programmatically using the REST API. The API is useful for automation or when managing multiple instances.
To set a CSP group:
```shell
curl --request PUT \
--header "PRIVATE-TOKEN: <your_access_token>" \
--header "Content-Type: application/json" \
--data '{"csp_namespace_id": 123456}' \
--url "https://gitlab.example.com/api/v4/admin/security/policy_settings"
```
To clear the CSP group:
```shell
curl --request PUT \
--header "PRIVATE-TOKEN: <your_access_token>" \
--header "Content-Type: application/json" \
--data '{"csp_namespace_id": null}' \
--url "https://gitlab.example.com/api/v4/admin/security/policy_settings"
```
To get the current CSP settings:
```shell
curl --request GET \
--header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/admin/security/policy_settings"
```
For more information, see the [policy settings API documentation](../api/policy_settings.md).
The selected group becomes your compliance and security policy (CSP) group, serving as the central place to manage security policies and compliance frameworks across your instance.
### Security policy management in the CSP group
See the [centralized security policy management](../user/application_security/policies/centralized_security_policy_management.md) documentation.
## User workflows
### Instance administrators
Instance administrators can:
1. **Designate a CSP group** from your existing top-level groups
1. **Create CSP security policies** in the designated group
1. **Configure policy scope** to determine where policies apply
1. **View policy coverage** to understand which policies are active across groups and projects
1. **Edit and manage** centralized policies as needed
### Group administrators and owners
Group administrators and owners can:
- View all applicable policies in **Secure** > **Policies**, including both locally-defined and centrally-managed policies.
- Create team-specific policies in addition to centrally-managed ones.
- Understand policy sources with clear indicators that show whether policies come from your team or central administration.
{{< alert type="note" >}}
The **Policies** page displays only the policies from the CSP that are currently applied to your group.
{{< /alert >}}
### Project administrators and owners
Project administrators and owners can:
- View all applicable policies in **Secure** > **Policies**, including both locally-defined and centrally-managed policies.
- Create project-specific policies in addition to centrally-managed ones.
- Understand policy sources with clear indicators that show whether policies come from your project, group, or central administration.
{{< alert type="note" >}}
The **Policies** page displays only the policies from the CSP that are currently applied to your group.
{{< /alert >}}
### Developers
Developers can:
- View all security policies that apply to your work in the **Secure** > **Policies**.
- Understand security and compliance requirements with clear visibility into centrally-mandated policies.
## Beta considerations
- Performance testing: While using a CSP in not expected to impact performance, comprehensive performance testing is ongoing.
- Mixed permission scenarios: Some edge cases with mixed permissions may require additional validation
- User experience: Some UI elements may not be fully polished and could change in future releases
- Compliance framework scoping: Scoping policies to compliance frameworks is not supported in the Beta release.
## Troubleshooting
**Unable to designate CSP group**
- Verify that you have instance administrator privileges.
- Verify that the group is a top-level group (not a subgroup).
- Verify that the group exists and is accessible.
## Feedback and support
As this is a Beta release, we actively seek feedback from users. Share your experience, suggestions, and any issues through:
- [GitLab Issues](https://gitlab.com/gitlab-org/gitlab/-/issues).
- Your regular GitLab support channels.

View File

@ -292,7 +292,9 @@ build:
##### pip
If your project provides a `requirements.txt` lock file generated by the [pip-compile command line tool](https://pip-tools.readthedocs.io/en/latest/cli/pip-compile/), the Dependency Scanning analyzer can extract the list of components and the dependency graph information, which provides support for the [dependency path](../../dependency_list/_index.md#dependency-paths) feature.
If your project provides a `requirements.txt` lock file generated by the [pip-compile command line tool](https://pip-tools.readthedocs.io/en/latest/cli/pip-compile/),
the Dependency Scanning analyzer can extract the list of components and the dependency graph information,
which provides support for the [dependency path](../../dependency_list/_index.md#dependency-paths) feature.
Alternatively, your project can provide a `pipdeptree.json` dependency graph export generated by the [`pipdeptree --json` command line utility](https://pypi.org/project/pipdeptree/).
@ -322,6 +324,11 @@ build:
paths: ["**/pipdeptree.json"]
```
Because of a [known issue](https://github.com/tox-dev/pipdeptree/issues/107), `pipdeptree` does not mark
[optional dependencies](https://setuptools.pypa.io/en/latest/userguide/dependency_management.html#optional-dependencies)
as dependencies of the parent package. As a result, Dependency Scanning marks them as direct dependencies of the project,
instead of as transitive dependencies.
##### Pipenv
If your project provides only a `Pipfile.lock` file, the Dependency Scanning analyzer can still extract the list of components. However, [dependency path](../../dependency_list/_index.md#dependency-paths) information is not available.

View File

@ -17,6 +17,7 @@ title: Static reachability analysis
- [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/14177) as an [experiment](../../../policy/development_stages_support.md) in GitLab 17.5.
- [Changed](https://gitlab.com/groups/gitlab-org/-/epics/15781) from experiment to beta in GitLab 17.11.
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/502334) support for JavaScript and TypeScript in GitLab 18.2 and Dependency Scanning Analyzer v0.32.0.
{{< /history >}}
@ -32,20 +33,23 @@ project.
Prerequisites:
- Only Python projects are supported.
- Only Python, JavaScript, and TypeScript projects are supported.
- [Dependency Scanning analyzer](https://gitlab.com/gitlab-org/security-products/analyzers/dependency-scanning)
version 0.23.0 and later.
version 0.32.0 and later.
- Enable [Dependency Scanning by using SBOM](dependency_scanning_sbom/_index.md#getting-started).
[Gemnasium](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium) analyzers are not
supported.
Follow the [pip](dependency_scanning_sbom/_index.md#pip) or
For Python, follow the [pip](dependency_scanning_sbom/_index.md#pip) or
[pipenv](dependency_scanning_sbom/_index.md#pipenv)
related instructions for dependency scanning using SBOM. You can also use any other Python package
manager that is
[supported](https://gitlab.com/gitlab-org/security-products/analyzers/dependency-scanning#supported-files)
by the DS analyzer.
For JavaScript and TypeScript, ensure your repository has the [supported](https://gitlab.com/gitlab-org/security-products/analyzers/dependency-scanning#supported-files)
lock files by the DS analyzer.
Exclusions:
- SRA cannot be used together with either a scan execution policy or pipeline execution policy.
@ -104,12 +108,24 @@ When a direct dependency is marked as **in use**, all its transitive dependencie
## Supported languages and package managers
Static reachability analysis is available only for Python projects. SRA uses the new dependency
scanning analyzer to generate SBOMs and so supports the same package managers as the analyzer.
Static reachability analysis is available for Python, JavaScript, and TypeScript projects. SRA uses the new dependency
scanning analyzer to generate SBOMs and supports the same package managers as the analyzer.
| Language | Supported Package Managers |
|----------|----------------------------|
| Python | `pip`, `pipenv`, `poetry`, `uv` |
| Language | Supported package managers | Supported file suffix |
|----------|----------------------------|-----------------------|
| Python | `pip`, `pipenv`, `poetry`, `uv` | `.py` |
| JavaScript/TypeScript | `npm`, `pnpm`, `yarn` | `.js`, `.ts` |
For Python `pipenv`, static reachability doesn't support `Pipfile.lock` files. Support is available only for `pipenv.graph.json` because it has support for a dependency graph.
If a package manager without dependency graph support is used, all indirect dependencies are marked as not in use.
Because of a [known issue](dependency_scanning_sbom/_index.md#pip) in Dependency Scanning with `pipdeptree`,
[optional dependencies](https://setuptools.pypa.io/en/latest/userguide/dependency_management.html#optional-dependencies)
are marked as direct dependencies instead of as transitive dependencies. Static Reachability might not
identify those packages as in use.
For example, requiring `passlib[bcrypt]` may result in `passlib` being marked as `in_use` whilst `bcrypt` is marked as `not_found`.
## Running SRA in an offline environment

View File

@ -57,6 +57,7 @@ To enforce policies to meet your requirements, consider the following factors:
- **Inheritance**: By default, a policy is enforced on the organizational units it's linked to, and
all their descendent subgroups and their projects.
- **Scope**: To customize policy enforcement, you can define a policy's scope to match your needs.
- **Centralized security policy management**: To apply security policies across multiple groups and projects from a single, centralized location, GitLab Self-Managed instance administrators can designate a compliance and security policy (CSP) group. For more information, see [centralized security policy management](centralized_security_policy_management.md).
#### Inheritance

View File

@ -0,0 +1,104 @@
---
stage: Security Risk Management
group: Security Policies
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
description: Learn how to apply security policies across multiple groups and projects from a single, centralized location.
title: Centralized security policy management
---
{{< details >}}
- Tier: Ultimate
- Offering: GitLab Self-Managed
- Status: Beta
{{< /details >}}
{{< history >}}
- [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/7622) in GitLab 18.2 [with a feature flag](../../../administration/feature_flags/_index.md) named `security_policies_csp`. Disabled by default.
{{< /history >}}
{{< alert type="flag" >}}
The availability of this feature is controlled by a feature flag.
For more information, see the history.
This feature is subject to change and may not ready for production use.
{{< /alert >}}
Centralized security policy management allows instance administrators to designate a compliance and security policy (CSP) group and apply security policies across multiple groups and projects from a single, centralized location.
When you create or edit a security policy in the CSP group, you can scope it to:
- **Specific groups and subgroups**: Apply the policy only to selected groups and their subgroups.
- **Specific projects**: Apply the policy to individual projects.
- **All projects in the instance**: Apply the policy across your entire GitLab instance.
- **All projects with exceptions**: Apply to all projects except those you specify.
When you designate a CSP group to serve as your centralized policy management hub, you can:
- Create and configure security policies that automatically apply across your instance.
- Scope policies to specific groups, projects, or your entire instance.
- View comprehensive policy coverage to understand which policies are active and where they're active.
- Maintain centralized control while allowing teams to create their own additional policies.
## Prerequisites
- GitLab Self-Managed.
- GitLab 18.2 or later.
- You must be instance administrator.
- You must have an existing top-level group to serve as the CSP group.
- To use the REST API (optional), you must have a token with administrator access.
## Set up centralized security policy management
To set up centralized security policy management, you designate a CSP group and then create policies in the group.
For more information, see [instance-wide compliance and security policy management](../../../security/compliance_security_policy_management.md).
### Create security policies in the CSP group
To create the policies:
1. Go to your designated CSP group.
1. Go to **Secure** > **Policies**.
1. Create one or more security policies as you typically would. Before you save each policy:
- In the **Policy scope** section, select a scope to apply the policy to:
- **Groups**: Apply the policy to specific groups and subgroups.
- **Projects**: Apply the policy individual projects.
- **All projects**: Apply to the entire instance.
- **All projects except**: Apply to all projects with specified exceptions.
1. Save your policy configuration.
## Policy storage and configuration
CSP policies are stored in a `policy.yml` file in the designated CSP group, similar to how group policies are managed. CSP policies use the same configuration format as existing security policies.
## Policy synchronization
- Depending on the number of groups and projects in scope, policy changes may take some time to apply across your instance.
- The synchronization process uses background jobs that are automatically queued when you designate a CSP group, create policies, or update policies.
- Instance administrators can monitor background job processing in **Admin Area** > **Monitoring** > **Background jobs**.
- To verify that policies are successfully applied in a target group or project, go to **Secure** > **Policies** in the group or project.
## Troubleshooting
**Policy does not appear in the target group or project**
- Verify that the policy scope includes the target group or project.
- Verify that the CSP group is properly designated in the admin settings.
- Verify that the policy is enabled in the CSP group.
- Policy changes may take time to be applied. See [policy synchronization](#policy-synchronization) for more information.
**Performance concerns**
- Monitor policy propagation times, especially with large scope configurations.
- Consider scoping policies to specific groups or projects instead of applying the policies to all projects.
## Feedback and support
As this is a Beta release, we actively seek feedback from users. Share your experience, suggestions, and any issues through:
- [GitLab Issues](https://gitlab.com/gitlab-org/gitlab/-/issues).
- Your regular GitLab support channels.

View File

@ -539,6 +539,7 @@ Audit event types belong to the following product categories.
| [`security_policy_delete`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/192797) | A security policy is deleted | {{< icon name="check-circle" >}} Yes | GitLab [18.1](https://gitlab.com/gitlab-org/gitlab/-/issues/539230) | Project |
| [`security_policy_update`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/192797) | A security policy is updated | {{< icon name="check-circle" >}} Yes | GitLab [18.1](https://gitlab.com/gitlab-org/gitlab/-/issues/539230) | Project |
| [`merge_request_branch_bypassed_by_security_policy`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/195942) | The merge request's approval is bypassed by the branches configured in the security policy | {{< icon name="check-circle" >}} Yes | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/issues/549646) | Project |
| [`merge_request_merged_with_policy_violations`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/195775) | A merge request merged with security policy violations | {{< icon name="check-circle" >}} Yes | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/work_items/549813) | Project |
| [`policy_violations_detected`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/193482) | Security policy violation is detected in the merge request | {{< icon name="dotted-circle" >}} No | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/work_items/549811) | Project |
| [`policy_violations_resolved`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/193482) | Security policy violations are resolved in the merge request | {{< icon name="dotted-circle" >}} No | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/issues/549812) | Project |

View File

@ -292,7 +292,7 @@ To link the SAML groups:
{{< history >}}
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/480766) for GitLab.com in GitLab 17.8 [with a flag](../../../administration/feature_flags/_index.md) named `saml_groups_duo_pro_add_on_assignment`. Disabled by default.
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/512141) for Self-Managed in GitLab 17.11.
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/512141) for Self-Managed in GitLab 18.0.
{{< /history >}}
Prerequisites:

View File

@ -35,9 +35,7 @@ You can use Docker commands to build and push container images to your container
## Use GitLab CI/CD
You can use [GitLab CI/CD](../../../ci/_index.md) to build and push container images to the
Container Registry. You can use CI/CD to test, build, and deploy your project from the container
image you created.
Use [GitLab CI/CD](../../../ci/_index.md) to build, push, test, and deploy container images from the container registry.
### Configure your `.gitlab-ci.yml` file
@ -72,6 +70,8 @@ Prerequisites:
{{< tab title="From the container registry" >}}
Use this approach when you want to use images stored in your GitLab container registry.
In your `.gitlab-ci.yml` file:
- Update `image` and `services` to point to your registry.
@ -95,9 +95,11 @@ build:
{{< tab title="With the Dependency Proxy" >}}
Use this approach to cache images from external registries like Docker Hub for faster builds and to avoid rate limits.
In your `.gitlab-ci.yml` file:
- Update `image` and `services` to point to your dependency proxy.
- Update `image` and `services` to use the Dependency Proxy prefix.
- Add a service [alias](../../../ci/services/_index.md#available-settings-for-services).
Your `.gitlab-ci.yml` should look similar to this:

View File

@ -429,7 +429,7 @@ When they are imported, supported GitHub branch protection rules are mapped to e
|:----------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------|
| **Require conversation resolution before merging** for the project's default branch | **All threads must be resolved** [project setting](../merge_requests/_index.md#prevent-merge-unless-all-threads-are-resolved) | [GitLab 15.5](https://gitlab.com/gitlab-org/gitlab/-/issues/371110) |
| **Require a pull request before merging** | **No one** option in the **Allowed to push and merge** list of [branch protection settings](../repository/branches/protected.md#protect-a-branch) | [GitLab 15.5](https://gitlab.com/gitlab-org/gitlab/-/issues/370951) |
| **Require signed commits** for the project's default branch | **Reject unsigned commits** GitLab [push rule](../repository/push_rules.md#prevent-unintended-consequences) | [GitLab 15.5](https://gitlab.com/gitlab-org/gitlab/-/issues/370949) |
| **Require signed commits** for the project's default branch | **Reject unsigned commits** GitLab [push rule](../repository/push_rules.md#require-signed-commits) | [GitLab 15.5](https://gitlab.com/gitlab-org/gitlab/-/issues/370949) |
| **Allow force pushes - Everyone** | **Allowed to force push** [branch protection setting](../repository/branches/protected.md#allow-force-push) | [GitLab 15.6](https://gitlab.com/gitlab-org/gitlab/-/issues/370943) |
| **Require a pull request before merging - Require review from Code Owners** | **Require approval from code owners** [branch protection setting](../repository/branches/protected.md#require-code-owner-approval) | [GitLab 15.6](https://gitlab.com/gitlab-org/gitlab/-/issues/376683) |
| **Require a pull request before merging - Allow specified actors to bypass required pull requests** | List of users in the **Allowed to push and merge** list of [branch protection settings](../repository/branches/protected.md#protect-a-branch). Without a **Premium** subscription, the list of users that are allowed to push and merge is limited to roles. | [GitLab 15.8](https://gitlab.com/gitlab-org/gitlab/-/issues/384939) |

View File

@ -0,0 +1,186 @@
---
stage: Create
group: Source Code
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
description: View information about a repository's commit history.
title: Commits
---
{{< details >}}
- Tier: Free, Premium, Ultimate
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
{{< /details >}}
The **Commits** list displays the commit history for your repository. Use it to browse
code changes, view commit details, and verify commit signatures. You can filter the commit list by
Git revision to see the changes for a specific revision.
The list shows:
- Commit hash: Unique identifier (SHA) for each commit.
- Commit message: Title and description of the commit.
- Author: Name and avatar of the user who made the commit.
- Timestamp: When the commit was created.
- Pipeline status: CI/CD pipeline results, if configured.
- Signature verification: GPG, SSH, or X.509 signature status.
- Tags: Any tags pointing to this commit.
![An example of a repository's commits list](img/repository_commits_list_v18_2.png)
## View commits
To view your repository's commit history:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Code > Commits**.
To view a commit's summary, select the **Toggle commit description** icon ({{< icon name="ellipsis_h">}}).
This summary does not display file changes or statistics.
## View commit details
Examine the specific changes made in any commit, including file modifications, additions, and deletions.
To view a commit's details:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Code > Commits**.
1. Select the commit to open the commit's details page.
The commit's details page shows:
- Commit information: Commit hash, author, committer, parent commits, and timestamp.
- Commit message: Title and description of the commit.
- File changes: All modified files with diff view.
- Statistics: Number of lines changed, added, and removed.
- Pipeline details: Associated CI/CD pipeline status and details.
- References: Branches and tags containing this commit.
- Related merge requests: Links to merge requests associated with the commit.
## Filter and search commits
Filter and search the commit history to find specific changes or track work by particular authors.
### Filter by author
To filter commits by a specific author:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Code > Commits**.
1. In the **Author** dropdown list, select or search for the author's name or username.
If author filtering doesn't work for names with special characters, use the URL parameter format.
For example, append `?author=Elliot%20Stevens` to the URL.
### Filter by Git revision
To filter commits by Git revision, such as branch, tag, or commit SHA:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Code > Commits**.
1. In the dropdown list, select or search for a Git revision.
For example, branch name, tag, or commit SHA.
1. Select the Git revision to view the list of filtered commits.
### Search by commit message
To search for commits by message content:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Code > Commits**.
1. In the **Search by message** field, enter your search terms.
You can also search by commit SHA, full or partial, to find a specific commit directly.
## Cherry-pick a commit
Apply changes from a specific commit to another.
Prerequisites:
- You must have at least the Developer role for the project.
- The target branch must exist.
To cherry-pick a commit:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Code > Commits**.
1. Select the commit you want to cherry-pick.
1. In the upper-right corner, select **Options** and then **Cherry-pick**.
1. In the dialog:
- From the dropdown lists, select the target project and branch.
- Optional: Select **Start a new merge request** to create a merge request with the changes.
- Select **Cherry-pick**.
GitLab creates a new commit on the target branch with the cherry-picked changes.
If the branch is [protected](../branches/protected.md) or you don't have the correct permissions,
GitLab prompts you to [create a new merge request](../../merge_requests/_index.md#create-a-merge-request).
## Revert a commit
Create a new commit that undoes changes from a previous commit.
Prerequisites:
- You must have at least the Developer role for the project.
To revert a commit:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Code > Commits**.
1. Select the commit you want to revert.
1. In the upper-right corner, select **Options** and then **Revert**.
1. In the dialog:
- Select the target branch for the revert commit.
- Optional: Select **Start a new merge request** to create a merge request.
- Select **Revert**.
GitLab creates a new commit that reverses the changes from the selected commit.
If the branch is [protected](../branches/protected.md) or you don't have the correct permissions,
GitLab prompts you to [create a new merge request](../../merge_requests/_index.md#create-a-merge-request).
## Download commit contents
To download a commit's diff contents:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Code > Commits**.
1. Select the commit you want to download.
1. In the upper-right corner, select **Options**.
1. Under **Downloads**, select **Plain Diff**.
## Verify commit signatures
GitLab verifies GPG, SSH, and X.509 signatures to ensure commit authenticity.
Verified commits show a **Verified** badge.
For more information, see [signed commits](../signed_commits/_index.md).
### View signature details
To view signature information:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Code > Commits**.
1. Find a commit with a **Verified** or **Unverified** badge.
1. Select the badge to view signature details including:
- Signature type (GPG, SSH, or X.509)
- Key fingerprint
- Verification status
- Signer identity
## View pipeline status and details
The commit list includes a CI/CD pipeline status icon next to each commit. To view the pipeline details:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Code > Commits**.
1. Select the pipeline status icon next to any commit.
## Related topics
- [Signed commits](../signed_commits/_index.md)
- [Git file history](../files/git_history.md)
- [Tags](../tags/_index.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -29,6 +29,7 @@ can and can't be pushed to your repository. While GitLab offers
- Enforcing [branch name rules](branches/_index.md#name-your-branch).
- Evaluating the details of files.
- Preventing Git tag removal.
- Requiring signed commits.
GitLab uses [RE2 syntax](https://github.com/google/re2/wiki/Syntax) for regular expressions
in push rules. You can test them at the [regex101 regex tester](https://regex101.com/).
@ -118,13 +119,6 @@ Use these rules for your commit messages.
the expression. To allow any commit message, leave empty.
Uses multiline mode, which can be disabled by using `(?-m)`.
## Reject commits that aren't DCO certified
Commits signed with the [Developer Certificate of Origin](https://developercertificate.org/) (DCO)
certify the contributor wrote, or has the right to submit, the code contributed in that commit.
You can require all commits to your project to comply with the DCO. This push rule requires a
`Signed-off-by:` trailer in every commit message, and rejects any commits that lack it.
## Validate branch names
To validate your branch names, enter a regular expression for **Branch name**.
@ -160,9 +154,8 @@ Use these rules to prevent unintended consequences.
- **Reject unsigned commits**: Commit [must be signed](signed_commits/_index.md). This rule
can block some legitimate commits [created in the Web IDE](#reject-unsigned-commits-push-rule-disables-web-ide),
and allow [unsigned commits created in the GitLab UI](#unsigned-commits-created-in-the-gitlab-ui).
and allow [unsigned commits to appear in commit history](#unsigned-commits-appear-in-commit-history).
- **Do not allow users to remove Git tags with `git push`**: Users cannot use `git push` to remove Git tags.
Users can still delete tags in the UI.
## Validate files
@ -268,7 +261,7 @@ The regular expression can:
- Exclude specific file types by extension.
- Combine multiple expressions to exclude several patterns.
#### Regular expression examples
### Regular expression examples
These examples use common regex string boundary patterns:
@ -277,7 +270,7 @@ These examples use common regex string boundary patterns:
- `\.`: Matches a literal period character. The backslash escapes the period.
- `\/`: Matches a literal forward slash. The backslash escapes the forward slash.
##### Prevent specific file types
#### Prevent specific file types
- To prevent pushing `.exe` files to any location in the repository:
@ -285,7 +278,7 @@ These examples use common regex string boundary patterns:
\.exe$
```
##### Prevent specific files
#### Prevent specific files
- To prevent pushing a specific configuration file:
@ -307,7 +300,7 @@ These examples use common regex string boundary patterns:
(^|\/)install\.exe$
```
##### Combine patterns
#### Combine patterns
You can combine multiple patterns into one expression. This example combines all the previous expressions:
@ -315,12 +308,37 @@ You can combine multiple patterns into one expression. This example combines all
(\.exe|^config\.yml|^directory-name\/config\.yml|(^|\/)install\.exe)$
```
## Require signed commits
[Signed commits](signed_commits/_index.md) are digital signatures used to verify authenticity.
Use the **Reject unsigned commits** push rule to require all commits to have cryptographic signatures.
When you enable this rule:
- All new commits pushed to the repository must contain a valid cryptographic signature.
- The signature must be created using a supported signing method (GPG, SSH, or X.509).
- Commits without any signature are rejected at push time.
- Commits with invalid or corrupted signatures are rejected.
To enable the **Reject unsigned commits** push rule:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Settings > Repository**.
1. Expand **Push rules**.
1. Select **Reject unsigned commits**.
1. Select **Save push rules**.
## Reject commits that aren't DCO certified
Commits signed with the [Developer Certificate of Origin](https://developercertificate.org/) (DCO)
certify the contributor wrote, or has the right to submit, the code contributed in that commit.
You can require all commits to your project to comply with the DCO. This push rule requires a
`Signed-off-by:` trailer in every commit message, and rejects any commits that lack it.
## Related topics
- [Git server hooks](../../../administration/server_hooks.md) (previously called server hooks), to create complex custom push rules
- [Signing commits with GPG](signed_commits/gpg.md)
- [Signing commits with SSH](signed_commits/ssh.md)
- [Signing commits with X.509](signed_commits/x509.md)
- [Signed commits](signed_commits/_index.md)
- [Protected branches](branches/protected.md)
- [Secret detection](../../application_security/secret_detection/_index.md)
@ -338,14 +356,14 @@ must disable the feature flag `reject_unsigned_commits_by_gitlab` [with a flag](
Feature.disable(:reject_unsigned_commits_by_gitlab)
```
### Unsigned commits created in the GitLab UI
### Unsigned commits appear in commit history
The **Reject unsigned commits** push rule ignores commits that are authenticated
and created by GitLab (either through the UI or API). When this push rule is
enabled, unsigned commits may still appear in the commit history if a commit was
created in GitLab itself. As expected, commits created outside GitLab and
pushed to the repository are rejected. For more information about this issue,
read [issue #19185](https://gitlab.com/gitlab-org/gitlab/-/issues/19185).
The **Reject unsigned commits** push rule ignores commits that are authenticated and created by
GitLab (either through the UI or API). When this push rule is enabled, unsigned commits might still
appear in the commit history if a commit was created in GitLab itself.
As expected, commits created outside GitLab and pushed to the repository are rejected.
For more information, see [issue #5361](https://gitlab.com/gitlab-org/gitaly/-/issues/5361).
### Bulk update push rules for all projects

View File

@ -17,12 +17,15 @@ When you add a digital signature to your commit, you provide extra assurance tha
originated from you, rather than an impersonator. A digital signature is a cryptographic output
used to verify authenticity.
If GitLab can verify the committer's identity with a public [GPG key](gpg.md), the commit is
marked **Verified** in the GitLab UI.
You can then configure [push rules](../push_rules.md) for your project to:
It's important to understand the difference between signed and verified commits:
- Reject individual unsigned commits.
- Reject all commits from unverified users.
- Signed commits have a cryptographic signature attached that proves the commit's
integrity and authenticity. The signature is created using a private key.
- Verified commits have signatures that GitLab can validate against a known public key
stored in a user's GitLab profile.
If GitLab can verify the committer's identity with a public key, the commit is
marked **Verified** in the GitLab UI.
{{< alert type="note" >}}
@ -31,11 +34,11 @@ applies it. Commit signing verifies only the committer's identity.
{{< /alert >}}
Sign commits with your:
GitLab supports the following commit signing methods:
- [SSH key](ssh.md).
- [GPG key](gpg.md).
- [Personal x.509 certificate](x509.md).
- [SSH key](ssh.md)
- [GPG key](gpg.md)
- [Personal X.509 certificate](x509.md)
## Verify commits
@ -64,13 +67,13 @@ To review commits for a merge request, or for an entire project, and verify they
You can also [use the Commits API](../../../../api/commits.md#get-signature-of-a-commit)
to check a commit's signature.
### Verify commits made in the web UI
### Verify web UI commits
GitLab uses SSH to sign commits created through the web UI.
To verify these commits locally, obtain the GitLab public key for signing web commits
using the [Web Commits API](../../../../api/web_commits.md#get-public-signing-key).
### Use gitmailmap with verified commits
### Use `gitmailmap` with verified commits
{{< history >}}
@ -92,6 +95,14 @@ When using a `mailmap` author mapping, it's possible to have a verified commit w
For SSH and UI signatures with `mailmap` author mappings, GitLab displays an orange verified label with a warning sign.
To restore the green verified label, verify the mapped email address, or remove the `mailmap` entry.
## Enforce signed commits with push rules
You can require signed commits across your projects using push rules.
The **Reject unsigned commits** push rule prevents any unsigned commits from being pushed
to a repository, helping organizations maintain code integrity and meet compliance requirements.
For more information about how this rule works and its limitations, see [Require signed commits](../push_rules.md#require-signed-commits).
## Troubleshooting
### Fix verification problems with signed commits

View File

@ -13,6 +13,7 @@ module API
end
feature_category :webhooks
urgency :low
helpers ::API::Helpers::WebHooksHelpers

View File

@ -26533,6 +26533,9 @@ msgstr ""
msgid "Failed to load groups, users and deploy keys."
msgstr ""
msgid "Failed to load iterations. Please try again."
msgstr ""
msgid "Failed to load labels. Please try again."
msgstr ""
@ -66290,12 +66293,6 @@ msgstr ""
msgid "Update %{sourcePath} file"
msgstr ""
msgid "Update Now"
msgstr ""
msgid "Update Scheduled…"
msgstr ""
msgid "Update appearance settings"
msgstr ""
@ -66317,6 +66314,9 @@ msgstr ""
msgid "Update password for %{current_name}"
msgstr ""
msgid "Update scheduled…"
msgstr ""
msgid "Update selected"
msgstr ""

View File

@ -1,9 +1,10 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { GlSkeletonLoader } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import Vue from 'vue';
// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
import CrudComponent from '~/vue_shared/components/crud_component.vue';
import StatisticsPanelApp from '~/admin/statistics_panel/components/app.vue';
import statisticsLabels from '~/admin/statistics_panel/constants';
import createStore from '~/admin/statistics_panel/store';
@ -22,6 +23,9 @@ describe('Admin statistics app', () => {
const createComponent = () => {
wrapper = shallowMount(StatisticsPanelApp, {
store,
stubs: {
CrudComponent,
},
});
};
@ -35,11 +39,11 @@ describe('Admin statistics app', () => {
describe('template', () => {
describe('when app is loading', () => {
it('renders a loading indicator', () => {
it('renders a skeleton loader', () => {
store.dispatch('requestStatistics');
createComponent();
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(true);
});
});

View File

@ -22,7 +22,6 @@ describe('error tracking settings project dropdown', () => {
'projects',
'projectSelectionLabel',
'selectedProject',
'token',
),
hasProjects: false,
isProjectInvalid: false,

View File

@ -45,6 +45,7 @@ describe('WorkItemBulkEditSidebar component', () => {
]),
provide: {
hasIssuableHealthStatusFeature: false,
hasIterationsFeature: false,
...provide,
},
propsData: {

View File

@ -6,6 +6,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { mockTracking } from 'helpers/tracking_helper';
import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants';
import { newWorkItemId } from '~/work_items/utils';
import DropdownContentsCreateView from '~/sidebar/components/labels/labels_select_widget/dropdown_contents_create_view.vue';
import groupLabelsQuery from '~/sidebar/components/labels/labels_select_widget/graphql/group_labels.query.graphql';
import projectLabelsQuery from '~/sidebar/components/labels/labels_select_widget/graphql/project_labels.query.graphql';
@ -26,7 +27,9 @@ import {
Vue.use(VueApollo);
const workItemId = 'gid://gitlab/WorkItem/1';
const mockFullPath = 'test-project-path';
const mockWorkItemId = 'gid://gitlab/WorkItem/1';
const mockWorkItemType = 'Task';
describe('WorkItemLabels component', () => {
/** @type {import('helpers/vue_test_utils_helper').ExtendedWrapper} */
@ -69,7 +72,10 @@ describe('WorkItemLabels component', () => {
workItemQueryHandler = workItemQuerySuccess,
searchQueryHandler = projectLabelsQueryHandler,
updateWorkItemMutationHandler = successUpdateWorkItemMutationHandler,
fullPath = mockFullPath,
workItemId = mockWorkItemId,
workItemIid = '1',
workItemType = mockWorkItemType,
} = {}) => {
wrapper = shallowMountExtended(WorkItemLabels, {
apolloProvider: createMockApollo([
@ -80,17 +86,17 @@ describe('WorkItemLabels component', () => {
]),
provide: {
canAdminLabel: true,
issuesListPath: 'test-project-path/issues',
issuesListPath: `${fullPath}/issues`,
epicsListPath: 'groups/some-group/-/epics',
labelsManagePath: 'test-project-path/labels',
labelsManagePath: `${fullPath}/labels`,
},
propsData: {
workItemId,
workItemIid,
canUpdate,
isGroup,
fullPath: 'test-project-path',
workItemType: 'Task',
fullPath,
workItemType,
},
});
};
@ -117,7 +123,7 @@ describe('WorkItemLabels component', () => {
const getMutationInput = (addLabelIds, removeLabelIds) => {
return {
input: {
id: workItemId,
id: mockWorkItemId,
labelsWidget: {
addLabelIds,
removeLabelIds,
@ -208,7 +214,7 @@ describe('WorkItemLabels component', () => {
expect(findWorkItemSidebarDropdownWidget().props('listItems')).toHaveLength(result);
expect(handler).toHaveBeenCalledWith({
fullPath: 'test-project-path',
fullPath: mockFullPath,
searchTerm,
});
expect(groupLabelsQueryHandler).not.toHaveBeenCalled();
@ -291,6 +297,31 @@ describe('WorkItemLabels component', () => {
);
});
it('update labels when labels are removed during create mode', async () => {
createComponent({
workItemId: newWorkItemId(mockWorkItemType),
workItemQueryHandler: workItemQueryWithLabelsHandler,
updateWorkItemMutationHandler: successRemoveLabelWorkItemMutationHandler,
});
await waitForPromises();
findRegularLabel().vm.$emit('close', label1Id);
await nextTick();
expect(wrapper.emitted('updateWidgetDraft')).toEqual([
[
{
workItemType: mockWorkItemType,
fullPath: mockFullPath,
labels: [mockLabels[1], mockLabels[2]],
},
],
]);
expect(successRemoveLabelWorkItemMutationHandler).not.toHaveBeenCalled();
});
it('update labels when labels are added or removed at same time', async () => {
createComponent({
workItemQueryHandler: workItemQueryWithFewLabelsHandler,
@ -500,8 +531,8 @@ describe('WorkItemLabels component', () => {
toggleText: 'No labels',
});
expect(findDropdownContentsCreateView().props()).toEqual({
attrWorkspacePath: 'test-project-path',
fullPath: 'test-project-path',
attrWorkspacePath: mockFullPath,
fullPath: mockFullPath,
labelCreateType: 'project',
searchKey: '',
workspaceType: 'project',

View File

@ -1094,7 +1094,7 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do
subject { helper.fork_button_data_attributes(project) }
where(:has_user, :project_already_forked, :has_forkable_groups, :expected) do
where(:has_user, :project_already_forked, :has_groups_allowing_project_creation, :expected) do
false | false | false | nil
true | false | false | data_attributes_without_user_fork_url
true | false | true | data_attributes_without_user_fork_url
@ -1111,7 +1111,7 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do
allow(user).to receive(:can?).with(:fork_project, project).and_return(true)
allow(user).to receive(:can?).with(:create_projects, anything).and_return(true)
allow(user).to receive(:already_forked?).with(project).and_return(project_already_forked)
allow(user).to receive(:has_forkable_groups?).and_return(has_forkable_groups)
allow(user).to receive(:has_groups_allowing_project_creation?).and_return(has_groups_allowing_project_creation)
allow(project).to receive(:forks_count).and_return(4)
allow(project).to receive(:full_path).and_return(project_path)

View File

@ -10,6 +10,43 @@ RSpec.describe User, feature_category: :user_profile do
include ExclusiveLeaseHelpers
include LdapHelpers
shared_examples 'checks for groups with project creation permission' do
let_it_be(:user) { create(:user) }
context 'when user does not belong in a group' do
it { is_expected.to be(false) }
end
context 'when user belongs in group allowing project creation' do
let_it_be(:group) do
create(:group, developers: [user], project_creation_level: Gitlab::Access::DEVELOPER_PROJECT_ACCESS)
end
it { is_expected.to be(true) }
end
context 'when user belongs in group not allowing project creation' do
let_it_be(:group) do
create(:group, developers: [user], project_creation_level: Gitlab::Access::NO_ONE_PROJECT_ACCESS)
end
it { is_expected.to be(false) }
context 'when group is shared with another group allowing project creation' do
let_it_be(:shared_group_link) do
create(
:group_group_link,
:developer,
shared_with_group: group,
shared_group: create(:group, project_creation_level: Gitlab::Access::DEVELOPER_PROJECT_ACCESS)
)
end
it { is_expected.to be(true) }
end
end
end
it_behaves_like 'having unique enum values'
describe 'modules' do
@ -2953,7 +2990,6 @@ RSpec.describe User, feature_category: :user_profile do
let_it_be_with_refind(:user) { create(:user) }
let_it_be_with_refind(:group) { create(:group, owners: user) }
it { expect(user.several_namespaces?).to be_truthy }
it { expect(user.authorized_groups).to contain_exactly(group) }
it { expect(user.owned_groups).to contain_exactly(group) }
it { expect(user.namespaces).to contain_exactly(user.namespace, group) }
@ -3022,19 +3058,10 @@ RSpec.describe User, feature_category: :user_profile do
end
end
describe 'group multiple owners' do
let_it_be(:user) { create :user }
let_it_be(:user2) { create :user }
let_it_be(:group) { create :group, owners: [user, user2] }
it { expect(user2.several_namespaces?).to be_truthy }
end
describe 'namespaced' do
let_it_be(:user) { create :user }
let_it_be(:project) { create(:project, namespace: user.namespace) }
it { expect(user.several_namespaces?).to be_falsey }
it { expect(user.namespaces).to contain_exactly(user.namespace) }
end
@ -4160,6 +4187,24 @@ RSpec.describe User, feature_category: :user_profile do
end
end
describe '#has_groups_allowing_project_creation?' do
subject { user.has_groups_allowing_project_creation? }
it_behaves_like 'checks for groups with project creation permission'
end
describe '#can_select_namespace?' do
subject { user.can_select_namespace? }
context 'when user is admin' do
let_it_be(:user) { create(:user, :admin) }
it { is_expected.to be(true) }
end
it_behaves_like 'checks for groups with project creation permission'
end
describe '#can_create_project?' do
let(:user) { create(:user) }