-### Remove compliance standards adherence dashboard
+### Replace compliance standards adherence dashboard with compliance status dashboard
@@ -759,7 +759,7 @@ In GitLab 17.11, we released:
These features give all of the same functionality as the compliance standards adherence dashboard, but you can
configure the adherence that you require.
-In GitLab 18.6, we'll remove the compliance standards adherence dashboard.
+In GitLab 18.6, we'll replace the compliance standards adherence dashboard with the compliance status dashboard for more accurate reporting on requirements and controls.
diff --git a/doc/user/application_security/dependency_scanning/dependency_scanning_sbom/_index.md b/doc/user/application_security/dependency_scanning/dependency_scanning_sbom/_index.md
index 5a9d7648484..8769a224930 100644
--- a/doc/user/application_security/dependency_scanning/dependency_scanning_sbom/_index.md
+++ b/doc/user/application_security/dependency_scanning/dependency_scanning_sbom/_index.md
@@ -205,6 +205,13 @@ go:build:
#### Gradle
+For Gradle projects use either of the following methods to create a dependency graph.
+
+- Nebula Gradle Dependency Lock Plugin
+- Gradle's HtmlDependencyReportTask
+
+##### Dependency Lock Plugin
+
To enable the CI/CD component on a Gradle project:
1. Edit the `build.gradle` or `build.gradle.kts` to use the
@@ -247,6 +254,71 @@ build:
```
+##### HtmlDependencyReportTask
+
+The [HtmlDependencyReportTask](https://docs.gradle.org/current/dsl/org.gradle.api.reporting.dependencies.HtmlDependencyReportTask.html)
+is an alternative way to get the list of dependencies for a Gradle project (tested with `gradle`
+versions 4 through 8). This method gives information about dependencies which are both transitive
+and direct. To enable use of this method with dependency scanning the artifact from running the
+`gradle htmlDependencyReport` task needs to be available.
+
+```yaml
+stages:
+ - build
+ - test
+
+# Define the image that contains Java and Gradle
+image: gradle:8.0-jdk11
+
+include:
+ - component: $CI_SERVER_FQDN/components/dependency-scanning/main@0
+
+build:
+ stage: build
+ script:
+ - gradle --init-script report.gradle htmlDependencyReport
+ # The gradle task writes the dependency report as a javascript file under
+ # build/reports/project/dependencies. Because the file has an un-standardized
+ # name, the after_script finds and renames the file to
+ # `gradle-html-dependency-report.js` copying it to the same directory as
+ # `build.gradle`
+ after_script:
+ - |
+ reports_dir=build/reports/project/dependencies
+ while IFS= read -r -d '' src; do
+ dest="${src%%/$reports_dir/*}/gradle-html-dependency-report.js"
+ cp $src $dest
+ done < <(find . -type f -path "*/${reports_dir}/*.js" -not -path "*/${reports_dir}/js/*" -print0)
+ # Pass html report artifact to subsequent dependency scanning stage.
+ artifacts:
+ paths:
+ - "**/gradle-html-dependency-report.js"
+
+```
+
+The command above uses the `report.gradle` file and can be supplied through `--init-script` or its contents can be added to `build.gradle` directly:
+
+```kotlin
+allprojects {
+ apply plugin: 'project-report'
+}
+```
+
+{{< alert type="note" >}}
+
+The dependency report may indicate that dependencies for some configurations `FAILED` to be
+resolved. In this case dependency scanning logs a warning but does not fail the job. If you prefer
+to have the pipeline fail if resolution failures are reported, add the following extra steps to the
+`build` example above.
+
+{{< /alert >}}
+
+```shell
+while IFS= read -r -d '' file; do
+ grep --quiet -E '"resolvable":\s*"FAILED' $file && echo "Dependency report has dependencies with FAILED resolution status" && exit 1
+done < <(find . -type f -path "*/gradle-html-dependency-report.js -print0)
+```
+
#### Maven
The following example `.gitlab-ci.yml` demonstrates how to enable the CI/CD
diff --git a/doc/user/group/import/direct_transfer_migrations.md b/doc/user/group/import/direct_transfer_migrations.md
index c5d84046ab1..f65c2264be0 100644
--- a/doc/user/group/import/direct_transfer_migrations.md
+++ b/doc/user/group/import/direct_transfer_migrations.md
@@ -67,10 +67,12 @@ GitLab 16.8.
have the `api` scope.
- For GitLab 15.0 and earlier source instances, the personal access token must
have both the `api` and `read_repository` scopes.
-- You must have the Owner role on the source group to migrate from.
-- You must have a role in the destination namespace that enables you to
- [create a subgroup](../subgroups/_index.md#create-a-subgroup) in that
- namespace.
+- You must have the required permissions on the source and destination instances. For:
+ - Most users, you need:
+ - The Owner role on the source group to migrate from.
+ - A role in the destination namespace that allows you to [create a subgroup](../subgroups/_index.md#create-a-subgroup) in that namespace.
+ - Administrators of both instances without the required roles, you can instead start the import by using
+ [the API](../../../api/bulk_imports.md#start-a-new-group-or-project-migration).
- To import project snippets, ensure snippets are
[enabled in the source project](../../snippets.md#change-default-visibility-of-snippets).
- To import items stored in object storage, you must either:
diff --git a/doc/user/project/merge_requests/duo_in_merge_requests.md b/doc/user/project/merge_requests/duo_in_merge_requests.md
index c4436ed57bc..283efd6f629 100644
--- a/doc/user/project/merge_requests/duo_in_merge_requests.md
+++ b/doc/user/project/merge_requests/duo_in_merge_requests.md
@@ -147,7 +147,7 @@ Provide feedback on this experimental feature in [issue 408991](https://gitlab.c
- Tier: Ultimate
- Add-on: GitLab Duo Enterprise
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
-- LLM: Anthropic [Claude 3.5 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet)
+- LLM: Anthropic [Claude 3.7 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-7-sonnet)
{{< /details >}}
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index d776b86aaee..297bb4c01cb 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -67908,6 +67908,9 @@ msgstr ""
msgid "WorkItem|No child items are currently open."
msgstr ""
+msgid "WorkItem|No custom fields have been archived. Archive custom fields to remove them from active work items while preserving their data."
+msgstr ""
+
msgid "WorkItem|No items"
msgstr ""
@@ -68196,6 +68199,9 @@ msgstr ""
msgid "WorkItem|This %{workItemType} will be closed when the following is merged."
msgstr ""
+msgid "WorkItem|This group has no active custom fields. Create a custom field to track data that matters to your team."
+msgstr ""
+
msgid "WorkItem|This work item is not available. It either doesn't exist or you don't have permission to view it."
msgstr ""
diff --git a/package.json b/package.json
index 4e036c3a967..c01bcb79ac2 100644
--- a/package.json
+++ b/package.json
@@ -66,7 +66,7 @@
"@gitlab/fonts": "^1.3.0",
"@gitlab/query-language-rust": "0.5.2",
"@gitlab/svgs": "3.126.0",
- "@gitlab/ui": "112.2.0",
+ "@gitlab/ui": "112.2.1",
"@gitlab/vue-router-vue3": "npm:vue-router@4.5.0",
"@gitlab/vuex-vue3": "npm:vuex@4.1.0",
"@gitlab/web-ide": "^0.0.1-dev-20250401183248",
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 4284f9a359a..1b53d3f0d31 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -9957,4 +9957,20 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
let_it_be(:model) { create(:project, pool_repository: parent) }
end
end
+
+ describe '#valid_lfs_oids' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:lfs_object) { create(:lfs_object) }
+ let_it_be(:another_lfs_object) { create(:lfs_object) }
+
+ let(:oids) { [lfs_object.oid, another_lfs_object.oid] }
+
+ before do
+ create(:lfs_objects_project, lfs_object: lfs_object, project: project)
+ end
+
+ it 'returns only the OIDs of LFS objects owned by the project' do
+ expect(project.valid_lfs_oids(oids)).to eq([lfs_object.oid])
+ end
+ end
end
diff --git a/spec/services/merge_requests/link_lfs_objects_service_spec.rb b/spec/services/merge_requests/link_lfs_objects_service_spec.rb
index 9762b600eab..66965ea01c3 100644
--- a/spec/services/merge_requests/link_lfs_objects_service_spec.rb
+++ b/spec/services/merge_requests/link_lfs_objects_service_spec.rb
@@ -61,6 +61,42 @@ RSpec.describe MergeRequests::LinkLfsObjectsService, :sidekiq_inline, feature_ca
execute
end
+
+ context 'when there are an LFS object that does not belong to the source project' do
+ before do
+ allow_next_instance_of(Gitlab::Git::LfsChanges) do |instance|
+ allow(instance).to receive(:new_pointers)
+ .and_return([
+ instance_double(Gitlab::Git::Blob, lfs_oid: "8b12507783d5becacbf2ebe5b01a60024d8728a8f86dcc818bce699e8b3320bc"),
+ instance_double(Gitlab::Git::Blob, lfs_oid: "94a72c074cfe574742c9e99e863322f73feff82981d065ff65a0308f44f19f62"),
+ instance_double(Gitlab::Git::Blob, lfs_oid: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") # this LFS object doesn't belong to the source project
+ ])
+ end
+ end
+
+ it 'only links LFS objects that belong to the source project' do
+ expect_next_instance_of(Projects::LfsPointers::LfsLinkService) do |service|
+ expect(service).to receive(:execute).with(
+ %w[
+ 8b12507783d5becacbf2ebe5b01a60024d8728a8f86dcc818bce699e8b3320bc
+ 94a72c074cfe574742c9e99e863322f73feff82981d065ff65a0308f44f19f62
+ ])
+ end
+
+ execute
+ end
+
+ it 'calls valid_lfs_oids method two times when BATCH_SIZE is 2' do
+ stub_const("#{described_class}::BATCH_SIZE", 2)
+
+ expect(source_project)
+ .to receive(:valid_lfs_oids)
+ .twice
+ .and_call_original
+
+ execute
+ end
+ end
end
context 'but there are no LFS objects added' do
diff --git a/spec/services/snippets/repository_validation_service_spec.rb b/spec/services/snippets/repository_validation_service_spec.rb
index d8acb09fdb2..959b3dc46ad 100644
--- a/spec/services/snippets/repository_validation_service_spec.rb
+++ b/spec/services/snippets/repository_validation_service_spec.rb
@@ -12,66 +12,78 @@ RSpec.describe Snippets::RepositoryValidationService, feature_category: :source_
subject { service.execute }
- before do
- allow(repository).to receive(:branch_count).and_return(1)
- allow(repository).to receive(:ls_files).and_return(['foo'])
- allow(repository).to receive(:branch_names).and_return(['master'])
- end
-
- it 'returns error when the repository has more than one branch' do
- allow(repository).to receive(:branch_count).and_return(2)
-
- expect(subject).to be_error
- expect(subject.reason).to eq(described_class::INVALID_REPOSITORY)
- expect(subject.message).to match(/Repository has more than one branch/)
- end
-
- it 'returns error when existing branch name is not the default one' do
- allow(repository).to receive(:branch_names).and_return(['foo'])
-
- expect(subject).to be_error
- expect(subject.reason).to eq(described_class::INVALID_REPOSITORY)
- expect(subject.message).to match(/Repository has an invalid default branch name/)
- end
-
- it 'returns error when the repository has tags' do
- allow(repository).to receive(:tag_count).and_return(1)
-
- expect(subject).to be_error
- expect(subject.reason).to eq(described_class::INVALID_REPOSITORY)
- expect(subject.message).to match(/Repository has tags/)
- end
-
- it 'returns error when the repository has more file than the limit' do
- limit = Snippet.max_file_limit + 1
- files = Array.new(limit) { FFaker::Filesystem.file_name }
- allow(repository).to receive(:ls_files).and_return(files)
-
- expect(subject).to be_error
- expect(subject.reason).to eq(described_class::INVALID_REPOSITORY)
- expect(subject.message).to match(/Repository files count over the limit/)
- end
-
- it 'returns error when the repository has no files' do
- allow(repository).to receive(:ls_files).and_return([])
-
- expect(subject).to be_error
- expect(subject.reason).to eq(described_class::INVALID_REPOSITORY)
- expect(subject.message).to match(/Repository must contain at least 1 file/)
- end
-
- it 'returns error when the repository size is over the limit' do
- expect_next_instance_of(Gitlab::RepositorySizeChecker) do |checker|
- expect(checker).to receive(:above_size_limit?).and_return(true)
+ context 'when snippet is present' do
+ before do
+ allow(repository).to receive(:branch_count).and_return(1)
+ allow(repository).to receive(:ls_files).and_return(['foo'])
+ allow(repository).to receive(:branch_names).and_return(['master'])
end
- expect(subject).to be_error
- expect(subject.reason).to eq(described_class::INVALID_REPOSITORY)
- expect(subject.message).to match(/Repository size is above the limit/)
+ it 'returns error when the repository has more than one branch' do
+ allow(repository).to receive(:branch_count).and_return(2)
+
+ expect(subject).to be_error
+ expect(subject.reason).to eq(described_class::INVALID_REPOSITORY)
+ expect(subject.message).to match(/Repository has more than one branch/)
+ end
+
+ it 'returns error when existing branch name is not the default one' do
+ allow(repository).to receive(:branch_names).and_return(['foo'])
+
+ expect(subject).to be_error
+ expect(subject.reason).to eq(described_class::INVALID_REPOSITORY)
+ expect(subject.message).to match(/Repository has an invalid default branch name/)
+ end
+
+ it 'returns error when the repository has tags' do
+ allow(repository).to receive(:tag_count).and_return(1)
+
+ expect(subject).to be_error
+ expect(subject.reason).to eq(described_class::INVALID_REPOSITORY)
+ expect(subject.message).to match(/Repository has tags/)
+ end
+
+ it 'returns error when the repository has more file than the limit' do
+ limit = Snippet.max_file_limit + 1
+ files = Array.new(limit) { FFaker::Filesystem.file_name }
+ allow(repository).to receive(:ls_files).and_return(files)
+
+ expect(subject).to be_error
+ expect(subject.reason).to eq(described_class::INVALID_REPOSITORY)
+ expect(subject.message).to match(/Repository files count over the limit/)
+ end
+
+ it 'returns error when the repository has no files' do
+ allow(repository).to receive(:ls_files).and_return([])
+
+ expect(subject).to be_error
+ expect(subject.reason).to eq(described_class::INVALID_REPOSITORY)
+ expect(subject.message).to match(/Repository must contain at least 1 file/)
+ end
+
+ it 'returns error when the repository size is over the limit' do
+ expect_next_instance_of(Gitlab::RepositorySizeChecker) do |checker|
+ expect(checker).to receive(:above_size_limit?).and_return(true)
+ end
+
+ expect(subject).to be_error
+ expect(subject.reason).to eq(described_class::INVALID_REPOSITORY)
+ expect(subject.message).to match(/Repository size is above the limit/)
+ end
+
+ it 'returns success when no validation errors are raised' do
+ expect(subject).to be_success
+ end
end
- it 'returns success when no validation errors are raised' do
- expect(subject).to be_success
+ context 'when snippet does not exist' do
+ let_it_be(:snippet) { nil }
+
+ it 'returns error' do
+ expect(subject).to be_error
+ expect(subject.message).to match(/No snippet found/)
+ expect(subject.reason).to eq(described_class::SNIPPET_NOT_FOUND)
+ end
end
end
end
diff --git a/yarn.lock b/yarn.lock
index cbb33dfa50a..6081fef7d35 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1441,10 +1441,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.126.0.tgz#1c0bb95c11de808b78afd05dc95aca258c3b39f0"
integrity sha512-7X8uzitNn7NDcVy+FVCw8npMNEUpLGHTO5Z+BJZqVILj/FD+0WveYdPxAEVa9hXYQn5qXWM0ZAknzB9LM6Id8w==
-"@gitlab/ui@112.2.0":
- version "112.2.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-112.2.0.tgz#e60d175811f17873e70d066e9595077d83f77b58"
- integrity sha512-391UvEnhOwIGNTGUoxV86dsP8c/LH5+hhTAYMnl2y4U5CceW8Zeex4wmCVTejDfcxR+KubS7xrhzwsq8y2pvPg==
+"@gitlab/ui@112.2.1":
+ version "112.2.1"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-112.2.1.tgz#be5fa326f300dc61a48d1369c816f6797096b5ee"
+ integrity sha512-n4NGntUoiTMK0e51bLq5HzRT5Ncad8PtDujT9nb5n2ptNPVnzl94yJrH8e8X6SXbNDfJd827NtmdPNfZbJPgsQ==
dependencies:
"@floating-ui/dom" "1.4.3"
echarts "^5.3.2"