Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-09-18 15:10:19 +00:00
parent 3aab29eacb
commit 884d6a4ece
47 changed files with 446 additions and 193 deletions

View File

@ -53,9 +53,24 @@ GITALY_SERVER_VERSION @project_278964_bot6 @gitlab-org/maintainers/rails-backend
/ee/db/click_house/
/**/click(_|-)?house/
## We list db/ subfolders explicitly as we don't want to match Clickhouse files
[Database] @gitlab-org/maintainers/database
/db/
/ee/db/
/db/database_connections/
/ee/db/database_connections/
/db/docs/
/ee/db/docs/
/ee/db/embedding/
/ee/db/geo/
/ee/db/seeds/
/db/gitlab_schemas/
/ee/db/gitlab_schemas/
/db/*migrate/
/ee/db/*migrate/
/db/schema_migrations/
/ee/db/schema_migrations/
# The following two lines only match db/ root files
/db/*
/ee/db/*
/lib/gitlab/background_migration/
/ee/lib/ee/gitlab/background_migration/
/lib/gitlab/database/

View File

@ -2549,7 +2549,7 @@
# The following rules needs to be the same as the one for .review:rules:review-cleanup
# except that:
# - most rules re automatic here (i.e. no `when: manual`) and not allowed to fail (i.e. no `allow_failure: true`) here
# - we start review apps automatically for scheduled pipelines and when the `pipeline:run-review-app` label is set
# - several rules have `variables: *review-change-pattern` here
.review:rules:start-review-app-pipeline:
rules:
@ -2562,9 +2562,13 @@
- <<: *if-merge-request-labels-run-review-app
- <<: *if-dot-com-gitlab-org-merge-request
changes: *ci-review-patterns
when: manual
allow_failure: true
- <<: *if-dot-com-gitlab-org-merge-request
changes: *frontend-build-patterns
variables: *review-change-pattern
when: manual
allow_failure: true
- <<: *if-dot-com-gitlab-org-merge-request
changes: *controllers-patterns
variables: *review-change-pattern
@ -2582,6 +2586,8 @@
allow_failure: true
- <<: *if-dot-com-gitlab-org-merge-request
changes: *qa-patterns
when: manual
allow_failure: true
- <<: *if-dot-com-gitlab-org-merge-request
changes: *code-patterns
when: manual

View File

@ -160,8 +160,9 @@ gdk-qa-reliable:
QA_RUN_TYPE: gdk-qa-blocking
parallel: 10
rules:
- when: always
allow_failure: true
- if: '$CI_MERGE_REQUEST_LABELS =~ /devops::govern/'
- when: on_success
allow_failure: true
gdk-qa-reliable-with-load-balancer:
extends:

View File

@ -0,0 +1,28 @@
import Vue from 'vue';
// TODO: Review replacing this when a breadcrumbs ViewComponent has been created https://gitlab.com/gitlab-org/gitlab/-/issues/367326
export const injectVueAppBreadcrumbs = (router, BreadcrumbsComponent, apolloProvider = null) => {
const breadcrumbEls = document.querySelectorAll('nav .js-breadcrumbs-list li');
if (breadcrumbEls.length < 1) {
return false;
}
const breadcrumbEl = breadcrumbEls[breadcrumbEls.length - 1];
const lastCrumb = breadcrumbEl.children[0];
const nestedBreadcrumbEl = document.createElement('div');
breadcrumbEl.replaceChild(nestedBreadcrumbEl, lastCrumb);
return new Vue({
el: nestedBreadcrumbEl,
router,
apolloProvider,
render(createElement) {
return createElement(BreadcrumbsComponent, {
class: breadcrumbEl.className,
});
},
});
};

View File

@ -4,7 +4,7 @@ import { parseBoolean } from '~/lib/utils/common_utils';
import PerformancePlugin from '~/performance/vue_performance_plugin';
import Translate from '~/vue_shared/translate';
import RegistryBreadcrumb from '~/packages_and_registries/shared/components/registry_breadcrumb.vue';
import { renderBreadcrumb } from '~/packages_and_registries/shared/utils';
import { injectVueAppBreadcrumbs } from '~/lib/utils/breadcrumbs';
import { apolloProvider } from './graphql/index';
import RegistryExplorer from './pages/index.vue';
import createRouter from './router';
@ -88,7 +88,7 @@ export default () => {
});
return {
attachBreadcrumb: renderBreadcrumb(router, apolloProvider, RegistryBreadcrumb),
attachBreadcrumb: () => injectVueAppBreadcrumbs(router, RegistryBreadcrumb, apolloProvider),
attachMainComponent,
};
};

View File

@ -4,7 +4,7 @@ import { parseBoolean } from '~/lib/utils/common_utils';
import PerformancePlugin from '~/performance/vue_performance_plugin';
import Translate from '~/vue_shared/translate';
import RegistryBreadcrumb from '~/packages_and_registries/harbor_registry/components/harbor_registry_breadcrumb.vue';
import { renderBreadcrumb } from '~/packages_and_registries/shared/utils';
import { injectVueAppBreadcrumbs } from '~/lib/utils/breadcrumbs';
import createRouter from './router';
import HarborRegistryExplorer from './pages/index.vue';
@ -79,7 +79,7 @@ export default (id) => {
};
return {
attachBreadcrumb: renderBreadcrumb(router, null, RegistryBreadcrumb),
attachBreadcrumb: () => injectVueAppBreadcrumbs(router, RegistryBreadcrumb),
attachMainComponent,
};
};

View File

@ -4,7 +4,7 @@ import { parseBoolean } from '~/lib/utils/common_utils';
import { apolloProvider } from '~/packages_and_registries/package_registry/graphql/index';
import PackageRegistry from '~/packages_and_registries/package_registry/pages/index.vue';
import RegistryBreadcrumb from '~/packages_and_registries/shared/components/registry_breadcrumb.vue';
import { renderBreadcrumb } from '~/packages_and_registries/shared/utils';
import { injectVueAppBreadcrumbs } from '~/lib/utils/breadcrumbs';
import createRouter from './router';
Vue.use(Translate);
@ -60,7 +60,7 @@ export default () => {
});
return {
attachBreadcrumb: renderBreadcrumb(router, apolloProvider, RegistryBreadcrumb),
attachBreadcrumb: () => injectVueAppBreadcrumbs(router, RegistryBreadcrumb, apolloProvider),
attachMainComponent,
};
};

View File

@ -1,4 +1,3 @@
import Vue from 'vue';
import { queryToObject } from '~/lib/utils/url_utility';
import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
@ -47,28 +46,3 @@ export const getCommitLink = ({ project_path: projectPath, pipeline = {} }, isGr
return `../commit/${pipeline.sha}`;
};
export const renderBreadcrumb = (router, apolloProvider, RegistryBreadcrumb) => () => {
const breadCrumbEls = document.querySelectorAll('nav .js-breadcrumbs-list li');
const breadCrumbEl = breadCrumbEls[breadCrumbEls.length - 1];
const lastCrumb = breadCrumbEl.children[0];
const crumbs = [lastCrumb];
const nestedBreadcrumbEl = document.createElement('div');
breadCrumbEl.replaceChild(nestedBreadcrumbEl, lastCrumb);
return new Vue({
el: nestedBreadcrumbEl,
router,
apolloProvider,
components: {
RegistryBreadcrumb,
},
render(createElement) {
return createElement('registry-breadcrumb', {
class: breadCrumbEl.className,
props: {
crumbs,
},
});
},
});
};

View File

@ -14,6 +14,7 @@ import {
SCOPE_BLOB,
SCOPE_PROJECTS,
SCOPE_NOTES,
SCOPE_COMMITS,
SEARCH_TYPE_ADVANCED,
} from '../constants';
import IssuesFilters from './issues_filters.vue';
@ -21,6 +22,7 @@ import MergeRequestsFilters from './merge_requests_filters.vue';
import BlobsFilters from './blobs_filters.vue';
import ProjectsFilters from './projects_filters.vue';
import NotesFilters from './notes_filters.vue';
import CommitsFilters from './commits_filters.vue';
export default {
name: 'GlobalSearchSidebar',
@ -35,6 +37,7 @@ export default {
SidebarPortal,
DomElementListener,
SmallScreenDrawerNavigation,
CommitsFilters,
},
mixins: [glFeatureFlagsMixin()],
computed: {
@ -51,7 +54,6 @@ export default {
return this.currentScope === SCOPE_BLOB && this.searchType === SEARCH_TYPE_ADVANCED;
},
showProjectsFilters() {
// for now the feature flag is here. Since we have only one filter in projects scope
return this.currentScope === SCOPE_PROJECTS;
},
showNotesFilters() {
@ -61,6 +63,14 @@ export default {
this.glFeatures.searchNotesHideArchivedProjects
);
},
showCommitsFilters() {
// for now, the feature flag is placed here. Since we have only one filter in commits scope
return (
this.currentScope === SCOPE_COMMITS &&
this.searchType === SEARCH_TYPE_ADVANCED &&
this.glFeatures.searchCommitsHideArchivedProjects
);
},
showScopeNavigation() {
// showScopeNavigation refers to whether the scope navigation should be shown
// while the legacy navigation is being used and there are no search results
@ -86,6 +96,7 @@ export default {
<blobs-filters v-if="showBlobFilters" />
<projects-filters v-if="showProjectsFilters" />
<notes-filters v-if="showNotesFilters" />
<commits-filters v-if="showCommitsFilters" />
</sidebar-portal>
</section>
@ -100,6 +111,7 @@ export default {
<blobs-filters v-if="showBlobFilters" />
<projects-filters v-if="showProjectsFilters" />
<notes-filters v-if="showNotesFilters" />
<commits-filters v-if="showCommitsFilters" />
</div>
<small-screen-drawer-navigation class="gl-lg-display-none">
<scope-legacy-navigation />
@ -108,6 +120,7 @@ export default {
<blobs-filters v-if="showBlobFilters" />
<projects-filters v-if="showProjectsFilters" />
<notes-filters v-if="showNotesFilters" />
<commits-filters v-if="showCommitsFilters" />
</small-screen-drawer-navigation>
</section>
</template>

View File

@ -11,6 +11,7 @@ const scopes = {
MERGE_REQUESTS: 'merge_requests',
NOTES: 'notes',
BLOBS: 'blobs',
COMMITS: 'commits',
};
const filterParam = 'include_archived';

View File

@ -0,0 +1,18 @@
<script>
import ArchivedFilter from './archived_filter/index.vue';
import FiltersTemplate from './filters_template.vue';
export default {
name: 'CommitsFilters',
components: {
ArchivedFilter,
FiltersTemplate,
},
};
</script>
<template>
<filters-template>
<archived-filter class="gl-mb-5" />
</filters-template>
</template>

View File

@ -3,6 +3,7 @@ export const SCOPE_MERGE_REQUESTS = 'merge_requests';
export const SCOPE_BLOB = 'blobs';
export const SCOPE_PROJECTS = 'projects';
export const SCOPE_NOTES = 'notes';
export const SCOPE_COMMITS = 'commits';
export const LABEL_DEFAULT_CLASSES = [
'gl-display-flex',
'gl-flex-direction-row',

View File

@ -10,15 +10,7 @@ class Admin::JobsController < Admin::ApplicationController
push_frontend_feature_flag(:admin_jobs_filter_runner_type, type: :ops)
end
def index
# We need all builds for tabs counters
@all_builds = Ci::JobsFinder.new(current_user: current_user).execute
@scope = params[:scope]
@builds = Ci::JobsFinder.new(current_user: current_user, params: params).execute
@builds = @builds.eager_load_everything
@builds = @builds.page(params[:page]).per(BUILDS_PER_PAGE).without_count
end
def index; end
def cancel_all
Ci::Build.running_or_pending.each(&:cancel)

View File

@ -27,15 +27,7 @@ class Projects::JobsController < Projects::ApplicationController
feature_category :continuous_integration
urgency :low
def index
# We need all builds for tabs counters
@all_builds = Ci::JobsFinder.new(current_user: current_user, project: @project).execute
@scope = params[:scope]
@builds = Ci::JobsFinder.new(current_user: current_user, project: @project, params: params).execute
@builds = @builds.eager_load_everything
@builds = @builds.page(params[:page]).per(30).without_count
end
def index; end
def show
if @build.instance_of?(::Ci::Bridge)

View File

@ -34,7 +34,7 @@ class ProjectAuthorization < ApplicationRecord
private
def assign_is_unique
self.is_unique = true if Feature.enabled?(:write_project_authorizations_is_unique)
self.is_unique = true
end
end

View File

@ -89,10 +89,8 @@ module ProjectAuthorizations
add_delay = add_delay_between_batches?(entire_size: attributes.size, batch_size: BATCH_SIZE)
log_details(entire_size: attributes.size, batch_size: BATCH_SIZE) if add_delay
write_is_unique = Feature.enabled?(:write_project_authorizations_is_unique)
attributes.each_slice(BATCH_SIZE) do |attributes_batch|
attributes_batch.each { |attrs| attrs[:is_unique] = true } if write_is_unique
attributes_batch.each { |attrs| attrs[:is_unique] = true }
ProjectAuthorization.insert_all(attributes_batch)
perform_delay if add_delay

View File

@ -105,11 +105,7 @@ module Ci
end
def pipelines_created_after
if Feature.enabled?(:lower_interval_for_canceling_redundant_pipelines, project)
3.days.ago
else
1.week.ago
end
3.days.ago
end
# Finding the pipelines to cancel is an expensive task that is not well

View File

@ -4,7 +4,7 @@
%head{ omit_og ? { } : { prefix: "og: http://ogp.me/ns#" } }
%meta{ charset: "utf-8" }
%meta{ 'http-equiv' => 'X-UA-Compatible', content: 'IE=edge' }
%meta{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1' }
%meta{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
%title= page_title(site_name)
= Gon::Base.render_data(nonce: content_security_policy_nonce)
= yield :project_javascripts

View File

@ -1,7 +1,7 @@
!!! 5
%html{ lang: I18n.locale }
%head
%meta{ :content => "width=device-width, initial-scale=1, maximum-scale=1", :name => "viewport" }
%meta{ :content => "width=device-width, initial-scale=1", :name => "viewport" }
%title= yield(:title)
%style
= Rails.application.assets_manifest.find_sources('errors.css').first.to_s.html_safe

View File

@ -1,7 +1,7 @@
!!! 5
%html{ lang: I18n.locale }
%head
%meta{ :content => "width=device-width, initial-scale=1, maximum-scale=1", :name => "viewport" }
%meta{ :content => "width=device-width, initial-scale=1", :name => "viewport" }
%title= yield(:title)
= stylesheet_link_tag 'application_utilities'
%style

View File

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/422961
milestone: '16.4'
type: development
group: group::environments
default_enabled: false
default_enabled: true

View File

@ -1,8 +0,0 @@
---
name: lower_interval_for_canceling_redundant_pipelines
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129256
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/421925
milestone: '16.3'
type: development
group: group::pipeline execution
default_enabled: false

View File

@ -1,8 +0,0 @@
---
name: write_project_authorizations_is_unique
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/130299
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/424097
milestone: '16.4'
type: development
group: group::security policies
default_enabled: false

View File

@ -71,7 +71,7 @@ a single URL used by all Geo sites, including the primary.
is using the secondary proxying and set the `URL` field to the single URL.
Make sure the primary site is also using this URL.
In Kubernetes, you can use the same domain under `global.hosts.domain` as for the primary site.
In Kubernetes, you can [use the same domain under `global.hosts.domain` as for the primary site](https://docs.gitlab.com/charts/advanced/geo/index.html).
## Geo proxying with Separate URLs

View File

@ -18,7 +18,11 @@ for all projects that didn't have it already enabled.
Only the entire settings for an integration can be inherited. Per-field inheritance
is proposed in [epic 2137](https://gitlab.com/groups/gitlab-org/-/epics/2137).
## Manage instance-level default settings for a project integration **(FREE SELF)**
## Manage instance-level default settings for a project integration
Prerequisite:
- You must have administrator access to the instance.
To manage instance-level default settings for a project integration:
@ -58,6 +62,10 @@ is proposed in [epic 2137](https://gitlab.com/groups/gitlab-org/-/epics/2137).
### Remove an instance-level default setting
Prerequisite:
- You must have administrator access to the instance.
To remove an instance-level default setting:
1. On the left sidebar, select **Search or go to**.
@ -72,6 +80,10 @@ Resetting an instance-level default setting removes the integration from all pro
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218252) in GitLab 14.2.
Prerequisite:
- You must have administrator access to the instance.
To view projects in your instance that [use custom settings](#use-custom-settings-for-a-project-or-group-integration):
1. On the left sidebar, select **Search or go to**.
@ -80,7 +92,11 @@ To view projects in your instance that [use custom settings](#use-custom-setting
1. Select an integration.
1. Select the **Projects using custom settings** tab.
## Manage group-level default settings for a project integration
## Manage group-level default settings for a project integration **(FREE ALL)**
Prerequisite:
- You must have at least the Maintainer role for the group.
To manage group-level default settings for a project integration:
@ -119,6 +135,10 @@ is proposed in [epic 2137](https://gitlab.com/groups/gitlab-org/-/epics/2137).
### Remove a group-level default setting
Prerequisite:
- You must have at least the Maintainer role for the group.
To remove a group-level default setting:
1. On the left sidebar, select **Search or go to** and find your group.
@ -128,7 +148,11 @@ To remove a group-level default setting:
Resetting a group-level default setting removes integrations that use default settings and belong to a project or subgroup of the group.
## Use instance-level or group-level default settings for a project integration
## Use instance-level or group-level default settings for a project integration **(FREE ALL)**
Prerequisite:
- You must have at least the Maintainer role for the project.
To use instance-level or group-level default settings for a project integration:
@ -140,7 +164,11 @@ To use instance-level or group-level default settings for a project integration:
1. Complete the fields.
1. Select **Save changes**.
## Use custom settings for a project or group integration
## Use custom settings for a project or group integration **(FREE ALL)**
Prerequisite:
- You must have at least the Maintainer role for the project or group.
To use custom settings for a project or group integration:

View File

@ -504,6 +504,16 @@ This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
### `Query.memberRolePermissions`
List of all customizable permissions.
Returns [`CustomizablePermissionConnection`](#customizablepermissionconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
### `Query.mergeRequest`
Find a merge request.
@ -9217,6 +9227,29 @@ The edge type for [`CustomizableDashboardVisualization`](#customizabledashboardv
| <a id="customizabledashboardvisualizationedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="customizabledashboardvisualizationedgenode"></a>`node` | [`CustomizableDashboardVisualization`](#customizabledashboardvisualization) | The item at the end of the edge. |
#### `CustomizablePermissionConnection`
The connection type for [`CustomizablePermission`](#customizablepermission).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="customizablepermissionconnectionedges"></a>`edges` | [`[CustomizablePermissionEdge]`](#customizablepermissionedge) | A list of edges. |
| <a id="customizablepermissionconnectionnodes"></a>`nodes` | [`[CustomizablePermission]`](#customizablepermission) | A list of nodes. |
| <a id="customizablepermissionconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
#### `CustomizablePermissionEdge`
The edge type for [`CustomizablePermission`](#customizablepermission).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="customizablepermissionedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="customizablepermissionedgenode"></a>`node` | [`CustomizablePermission`](#customizablepermission) | The item at the end of the edge. |
#### `DastProfileConnection`
The connection type for [`DastProfile`](#dastprofile).
@ -15308,6 +15341,18 @@ Represents a product analytics dashboard visualization.
| <a id="customizabledashboardvisualizationslug"></a>`slug` | [`String!`](#string) | Slug of the visualization. |
| <a id="customizabledashboardvisualizationtype"></a>`type` | [`String!`](#string) | Type of the visualization. |
### `CustomizablePermission`
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="customizablepermissionavailablefor"></a>`availableFor` | [`[String!]!`](#string) | Objects the permission is available for. |
| <a id="customizablepermissiondescription"></a>`description` | [`String`](#string) | Description of the permission. |
| <a id="customizablepermissionname"></a>`name` | [`String!`](#string) | Localized name of the permission. |
| <a id="customizablepermissionrequirement"></a>`requirement` | [`String`](#string) | Requirement of the permission. |
| <a id="customizablepermissionvalue"></a>`value` | [`String!`](#string) | Value of the permission. |
### `DastPreScanVerification`
Represents a DAST Pre Scan Verification.

View File

@ -3226,6 +3226,36 @@ with the API scope enabled.
| `only_mirror_protected_branches`| boolean | No | Limits mirroring to only protected branches when set to `true`. |
| `mirror_branch_regex` | String | No | Contains a regular expression. Only branches with names matching the regex are mirrored. Requires `only_mirror_protected_branches` to be disabled. |
Example creating a project with pull mirroring:
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
--header "Content-Type: application/json" \
--data '{
"name": "new_project",
"namespace_id": "1",
"mirror": true,
"import_url": "https://username:token@gitlab.example.com/group/project.git"
}' \
--url "https://gitlab.example.com/api/v4/projects/"
```
Example adding pull mirroring:
```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/projects/:id" \
--data "mirror=true&import_url=https://username:token@gitlab.example.com/group/project.git"
```
Example removing pull mirroring:
```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/projects/:id" \
--data "mirror=false"
```
## Start the pull mirroring process for a Project **(PREMIUM ALL)**
> Moved to GitLab Premium in 13.9.

View File

@ -6,27 +6,25 @@ description: 'Cells: Contributions: Forks'
<!-- vale gitlab.FutureTense = NO -->
This document is a work-in-progress and represents a very early state of the
Cells design. Significant aspects are not documented, though we expect to add
them in the future. This is one possible architecture for Cells, and we intend to
contrast this with alternatives before deciding which approach to implement.
This documentation will be kept even if we decide not to implement this so that
we can document the reasons for not choosing this approach.
This document is a work-in-progress and represents a very early state of the Cells design.
Significant aspects are not documented, though we expect to add them in the future.
This is one possible architecture for Cells, and we intend to contrast this with alternatives before deciding which approach to implement.
This documentation will be kept even if we decide not to implement this so that we can document the reasons for not choosing this approach.
# Cells: Contributions: Forks
The [Forking workflow](../../../../user/project/repository/forking_workflow.md) allows users to copy existing Project sources into their own namespace of choice (Personal or Group).
The [forking workflow](../../../../user/project/repository/forking_workflow.md) allows users to copy existing Project sources into their own namespace of choice (personal or Group).
## 1. Definition
The [Forking workflow](../../../../user/project/repository/forking_workflow.md) is a common workflow with various usage patterns:
The [forking workflow](../../../../user/project/repository/forking_workflow.md) is a common workflow with various usage patterns:
- It allows users to contribute back to upstream Project.
- It persists repositories into their Personal Namespace.
- Users can copy to make changes and release as modified Project.
- It allows users to contribute back to an upstream Project.
- It persists repositories into their personal namespace.
- Users can copy a Project to make changes and release it as a modified Project.
Forks allow users not having write access to a parent Project to make changes.
The forking workflow is especially important for the open source community to contribute back to public Projects.
The forking workflow is especially important for the open-source community to contribute back to public Projects.
However, it is equally important in some companies that prefer a strong split of responsibilities and tighter access control.
The access to a Project is restricted to a designated list of developers.
@ -40,14 +38,45 @@ Forks enable:
The forking model is problematic in a Cells architecture for the following reasons:
- Forks are clones of existing repositories. Forks could be created across different Organizations, Cells and Gitaly shards.
- Users can create merge requests and contribute back to an upstream Project. This upstream Project might in a different Organization and Cell.
- Users can create merge requests and contribute back to an upstream Project. This upstream Project might be in a different Organization and Cell.
- The merge request CI pipeline is executed in the context of the source Project, but presented in the context of the target Project.
## 2. Data flow
## 2. Data exploration
## 3. Proposals
From a [data exploration](https://gitlab.com/gitlab-data/product-analytics/-/issues/1380), we retrieved the following information about existing forks:
### 3.1. Intra-Cluster forks
- Roughly 1.8m forks exist on GitLab.com at the moment.
- The majority of forks are under a personal namespace (82%).
- We were expecting a minimal use of forks within the same top-level Group and/or organization. Forking is only necessary for users who don't have permissions to access a Project. Inside companies we wouldn't expect teams to use forking workflows much unless they for some reason have different permissions across different team members. The data showed that only 9% of fork relationships have matching ultimate parent namespace identifiers (top-level Groups and personal namespaces). The other 91% of fork relationships are forked across different top-level namespaces. When trying to match top-level Groups to an identifiable company, we saw that:
- 3% of forked Projects are forked from an upstream Project in the same organization.
- 83% of forked Projects do not have an identifiable organization related to either up or downstream Project.
- The remaining 14% are forked from a source Project within a different company.
- 9% of top-level Groups (95k) with activity in the last 12 months have a project with a fork relationship, compared to 5% of top-level Groups (91k) with no activity in the last 12 months. We expect these top-level Groups to be impacted by Cells.
## 3. Proposal - Forks are created in a dedicated contribution space of the current Organization
Instead of creating Projects across Organizations, forks are created in a contribution space tied to the Organization.
A contribution space is similar to a personal namespace but rather than existing in the default Organization, it exists within the Organization someone is trying to contribute to.
Example:
- Any User that can view an Organization (all Users for public Organizations) can create a contribution space in the Organization. This is a dedicated namespace where they can create forks of Projects in that Organization. For example for `Produce Inc.` it could be `gitlab.com/organization/produce-inc/@ayufan`.
- To create a contribution space we do not require membership of an Organization as this would prevent open source workflows where contributors are able to fork and create a merge request without ever being invited to a Group or Project. We strictly respect visibility, so Users would not be able to create a fork in a private Organization without first being invited.
- When creating a fork for a Project Users will only be presented with the option to create forks in Groups that are part of the Organization. We will also give Users the option to create a contribution space and put the fork there. Today there is also a "Create a group" option when creating a fork. This functionality would also be limited to creating a new group in the organization to store the new fork.
- In order to support Users that want to fork without contributing back we might consider an option to create [an unlinked fork](../../../../user/project/repository/forking_workflow.md#unlink-a-fork) in any namespace they have permission to write to.
- The User has as many contribution spaces as Organizations they contribute to.
- The User cannot create additional personal Projects within contribution spaces. Personal Projects can continue to be created in their personal namespace.
- The Organization can prevent or disable usage of contribution spaces. This would disable forking by anyone that does not belong to a Group within the Organization.
- All current forks are migrated into the contribution space of the User in an Organization. Because this may result in data loss when the fork also has links to data outside of the upstream Project we will also keep the personal Project around as archived and remove the fork relationship.
- All forks are part of the Organization.
- Forks are not federated features.
- The contribution space and forked Project do not share configuration with the parent Project.
- If the Organization is deleted, the Projects containing forks will be moved either to the default Organization or we'll create a new Organization to house them, which is essentially a ghost Organization of the former Organization.
- Data in contribution spaces do not contribute to customer usage from a billing perspective.
- Today we do not have organization-scoped runners but if we do implement that they will likely need special settings for how or if they can be used by contribution space projects.
## 4. Alternative proposals considered
### 4.1. Intra-cluster forks
This proposal implements forks as intra-Cluster forks where communication is done via API between all trusted Cells of a cluster:
@ -59,48 +88,76 @@ This proposal implements forks as intra-Cluster forks where communication is don
- CI pipeline is fetched in the context of the source Project as it is today, the result is fetched into the merge request of the target Project.
- The Cell holding the target Project internally uses GraphQL to fetch the status of the source Project and includes in context of the information for merge request.
Upsides:
Pros:
- All existing forks continue to work as they are, as they are treated as intra-Cluster forks.
Downsides:
Cons:
- The purpose of Organizations is to provide strong isolation between Organizations. Allowing to fork across does break security boundaries.
- However, this is no different to the ability of users today to clone a repository to a local computer and push it to any repository of choice.
- Access control of source Project can be lower than those of target Project. Today, the system requires that in order to contribute back, the access level needs to be the same for fork and upstream.
- Access control of the source Project can be lower than that of the target Project. Today, the system requires that in order to contribute back, the access level needs to be the same for fork and upstream Project.
### 3.2. Forks are created in a Personal Namespace of the current Organization
Instead of creating Projects across Organizations, forks are created in a user's Personal Namespace tied to the Organization. Example:
- Each user that is part of an Organization receives their Personal Namespace. For example for `GitLab Inc.` it could be `gitlab.com/organization/gitlab-inc/@ayufan`.
- The user has to fork into their own Personal Namespace of the Organization.
- The user has as many Personal Namespaces as Organizations they belongs to.
- The Personal Namespace behaves similar to the currently offered Personal Namespace.
- The user can manage and create Projects within a Personal Namespace.
- The Organization can prevent or disable usage of Personal Namespaces, disallowing forks.
- All current forks are migrated into the Personal Namespace of user in an Organization.
- All forks are part of the Organization.
- Forks are not federated features.
- The Personal Namespace and forked Project do not share configuration with the parent Project.
### 3.3. Forks are created as internal Projects under current Projects
### 4.2. Forks are created as internal Projects under current Projects
Instead of creating Projects across Organizations, forks are attachments to existing Projects.
Each user forking a Project receives their unique Project. Example:
Each user forking a Project receives their unique Project.
Example:
- For Project: `gitlab.com/gitlab-org/gitlab`, forks would be created in `gitlab.com/gitlab-org/gitlab/@kamil-gitlab`.
- Forks are created in the context of the current Organization, they do not cross Organization boundaries and are managed by the Organization.
- Tied to the user (or any other user-provided name of the fork).
- Forks are not federated features.
Downsides:
Cons:
- Does not answer how to handle and migrate all existing forks.
- Might share current Group/Project settings, which could be breaking some security boundaries.
## 4. Evaluation
## 5. Evaluation
## 4.1. Pros
### 5.1. Pros
## 4.2. Cons
### 5.2. Cons
## 6. Example
As an example, we will demonstrate the impact of this proposal for the case that we move `gitlab-org/gitlab` to a different Organization.
`gitlab-org/gitlab` has [over 8K forks](https://gitlab.com/gitlab-org/gitlab/-/forks).
### Does this direction impact the canonical URLs of those forks?
Yes canonical URLs will change for forks.
Existing users that have forks in personal namespaces and want to continue contributing merge requests, will be required to migrate their fork to a new fork in a contribution space.
For example, a personal namespace fork at `https://gitlab.com/DylanGriffith/gitlab` will
need to be migrated to `https://gitlab.com/-/contributions/gitlab-inc/@DylanGriffith/gitlab`.
We may offer automated ways to move this, but manually the process would involve:
1. Create the contribution space fork
1. Push your local branch from your original fork to the new fork
1. Recreate any merge request that was still open and you wanted to merge
### Does it impact the Git URL of the repositories themselves?
Yes.
In the above the example the Git URL would change from
`gitlab.com:DylanGriffith/gitlab.git` to `gitlab.com:/-/contributions/gitlab-inc/@DylanGriffith/gitlab.git`.
### Would there be any user action required to accept their fork being moved within an Organization or towards a contribution space?
If we offer an automated process we'd present this as an option for the user as they will become the new owner of the contribution space.
### Can we make promises that we will not break the existing forks of public Projects hosted on GitLab.com?
Existing fork projects will not be deleted but their fork relationship will be
removed when the source project is moved to another Organization.
The owner of the open source project will be made aware that they will disconnect their
forks when they move the project which will require them to close all existing
merge requests from those forks.
There will need to be some process for keeping the history from these merge requests while effectively losing the ability to
collaborate on them or merge them.
In the case of `gitlab-org/gitlab` we will attempt to give as much notice of this process and make this process as transparent as possible.
When we make the decision to move this project to an Organization we will seek additional
feedback about what would be the minimum amount of automated migrations necessary to be acceptable here.
But the workflow for contributors will change after the move so this will be a punctuated event regardless.

View File

@ -3,7 +3,7 @@ status: proposed
creation-date: "2023-08-23"
authors: [ "@ayufan" ]
coach: "@grzegorz"
approvers: [ "@dhershkovitch", "@gabrielengel_gl", "@marknuzzo", "@nicolewilliams" ]
approvers: [ "@dhershkovitch", "@DarrenEastman", "@marknuzzo", "@nicolewilliams" ]
owning-stage: "~devops::verify"
participating-stages: [ ]
---

View File

@ -278,8 +278,8 @@ See `.review:rules:start-review-app-pipeline` in
[`rules.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/rules.gitlab-ci.yml) for
the specific list of rules.
If you want to force a Review App to be deployed regardless of your changes, you can add the
`pipeline:run-review-app` label to the merge request.
If you want to deploy a Review App in a merge request, you can either trigger the `start-review-app-pipeline` manual job in the CI/CD pipeline, or add the
`pipeline:run-review-app` label to the merge request and run a new CI/CD pipeline.
Consult the [Review Apps](../testing_guide/review_apps.md) dedicated page for more information.

View File

@ -6,21 +6,17 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Using review apps in the development of GitLab
Review apps are deployed using the `start-review-app-pipeline` job which triggers a child pipeline containing a series of jobs to perform the various tasks needed to deploy a review app.
Review apps are deployed using the `start-review-app-pipeline` manual job which triggers a child pipeline containing a series of jobs to perform the various tasks needed to deploy a review app.
![start-review-app-pipeline job](img/review-app-parent-pipeline.png)
For any of the following scenarios, the `start-review-app-pipeline` job would be automatically started:
- for merge requests with CI configuration changes
- for merge requests with frontend changes
- for merge requests with changes to `{,ee/,jh/}{app/controllers}/**/*`
- for merge requests with changes to `{,ee/,jh/}{app/models}/**/*`
- for merge requests with changes to `{,ee/,jh/}lib/{,ee/,jh/}gitlab/**/*`
- for merge requests with QA changes
- for scheduled pipelines
- the MR has the `pipeline:run-review-app` label set
For all other scenarios, the `start-review-app-pipeline` job can be triggered manually.
## E2E test runs on review apps
On every pipeline in the `qa` stage (which comes after the `review` stage), the `review-qa-smoke` and `review-qa-blocking` jobs are automatically started.

View File

@ -9616,9 +9616,6 @@ msgstr ""
msgid "Chat"
msgstr ""
msgid "Chat not available."
msgstr ""
msgid "ChatMessage|%{project_link}: Pipeline %{pipeline_link} of %{ref_type} %{ref_link} by %{user_combined_name} %{humanized_status} in %{duration}"
msgstr ""
@ -23754,6 +23751,9 @@ msgstr ""
msgid "I'm signing up for GitLab because:"
msgstr ""
msgid "I'm sorry, I was not able to find any documentation to answer your question."
msgstr ""
msgid "ID"
msgstr ""
@ -41778,6 +41778,9 @@ msgstr ""
msgid "Search or filter commits"
msgstr ""
msgid "Search or filter dependencies..."
msgstr ""
msgid "Search or filter results…"
msgstr ""

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<meta content="width=device-width, initial-scale=1" name="viewport">
<title>The page you're looking for could not be found (404)</title>
<style>
body {

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<meta content="width=device-width, initial-scale=1" name="viewport">
<title>The change you requested was rejected (422)</title>
<style>
body {

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<meta content="width=device-width, initial-scale=1" name="viewport">
<title>Something went wrong (500)</title>
<style>
body {

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<meta content="width=device-width, initial-scale=1" name="viewport">
<title>GitLab is not responding (502)</title>
<style>
body {

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<meta content="width=device-width, initial-scale=1" name="viewport">
<title>GitLab is not responding (503)</title>
<style>
body {

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<meta content="width=device-width, initial-scale=1" name="viewport">
<meta name="refresh" content="60">
<meta name="retry-after" content="100">
<meta name="robots" content="noindex, nofollow, noarchive, nostore">

View File

@ -14,8 +14,6 @@ RSpec.describe Admin::JobsController do
get :index
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:builds)).to be_a(Kaminari::PaginatableWithoutCount)
expect(assigns(:builds).count).to be(1)
end
end

View File

@ -47,7 +47,6 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state, featu
it 'has only pending builds' do
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:builds).first.status).to eq('pending')
end
end
@ -60,7 +59,6 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state, featu
it 'has only running jobs' do
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:builds).first.status).to eq('running')
end
end
@ -73,7 +71,6 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state, featu
it 'has only finished jobs' do
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:builds).first.status).to eq('success')
end
end
@ -89,7 +86,6 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state, featu
it 'redirects to the page' do
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:builds).current_page).to eq(last_page)
end
end
end

View File

@ -0,0 +1,84 @@
import { createWrapper } from '@vue/test-utils';
import Vue from 'vue';
import { injectVueAppBreadcrumbs } from '~/lib/utils/breadcrumbs';
import { resetHTMLFixture, setHTMLFixture } from 'helpers/fixtures';
import createMockApollo from 'helpers/mock_apollo_helper';
describe('Breadcrumbs utils', () => {
const breadcrumbsHTML = `
<nav>
<ul class="js-breadcrumbs-list">
<li>
<a href="/group-name" data-testid="existing-crumb">Group name</a>
</li>
<li>
<a href="/group-name/project-name/-/subpage" data-testid="last-crumb">Subpage</a>
</li>
</ul>
</nav>
`;
const emptyBreadcrumbsHTML = `
<nav>
<ul class="js-breadcrumbs-list" data-testid="breadcumbs-list">
</ul>
</nav>
`;
const mockRouter = jest.fn();
let MockComponent;
let mockApolloProvider;
beforeEach(() => {
MockComponent = Vue.component('MockComponent', {
render: (createElement) =>
createElement('span', {
attrs: {
'data-testid': 'mock-component',
},
}),
});
mockApolloProvider = createMockApollo();
});
afterEach(() => {
resetHTMLFixture();
MockComponent = null;
});
describe('injectVueAppBreadcrumbs', () => {
describe('without any breadcrumbs', () => {
beforeEach(() => {
setHTMLFixture(emptyBreadcrumbsHTML);
});
it('returns early and stops trying to inject', () => {
expect(injectVueAppBreadcrumbs(mockRouter, MockComponent)).toBe(false);
});
});
describe('with breadcrumbs', () => {
beforeEach(() => {
setHTMLFixture(breadcrumbsHTML);
});
describe.each`
testLabel | apolloProvider
${'set'} | ${mockApolloProvider}
${'not set'} | ${null}
`('given the apollo provider is $testLabel', ({ apolloProvider }) => {
beforeEach(() => {
createWrapper(injectVueAppBreadcrumbs(mockRouter, MockComponent, apolloProvider));
});
it('returns a new breadcrumbs component replacing the inject HTML', () => {
// Using `querySelectorAll` because we're not testing a full Vue app.
// We are testing a partial Vue app added into the pages HTML.
expect(document.querySelectorAll('[data-testid="existing-crumb"]')).toHaveLength(1);
expect(document.querySelectorAll('[data-testid="last-crumb"]')).toHaveLength(0);
expect(document.querySelectorAll('[data-testid="mock-component"]')).toHaveLength(1);
});
});
});
});
});

View File

@ -15,6 +15,7 @@ import MergeRequestsFilters from '~/search/sidebar/components/merge_requests_fil
import BlobsFilters from '~/search/sidebar/components/blobs_filters.vue';
import ProjectsFilters from '~/search/sidebar/components/projects_filters.vue';
import NotesFilters from '~/search/sidebar/components/notes_filters.vue';
import CommitsFilters from '~/search/sidebar/components/commits_filters.vue';
import ScopeLegacyNavigation from '~/search/sidebar/components/scope_legacy_navigation.vue';
import SmallScreenDrawerNavigation from '~/search/sidebar/components/small_screen_drawer_navigation.vue';
import ScopeSidebarNavigation from '~/search/sidebar/components/scope_sidebar_navigation.vue';
@ -45,6 +46,7 @@ describe('GlobalSearchSidebar', () => {
provide: {
glFeatures: {
searchNotesHideArchivedProjects: true,
searchCommitsHideArchivedProjects: true,
},
},
});
@ -56,6 +58,7 @@ describe('GlobalSearchSidebar', () => {
const findBlobsFilters = () => wrapper.findComponent(BlobsFilters);
const findProjectsFilters = () => wrapper.findComponent(ProjectsFilters);
const findNotesFilters = () => wrapper.findComponent(NotesFilters);
const findCommitsFilters = () => wrapper.findComponent(CommitsFilters);
const findScopeLegacyNavigation = () => wrapper.findComponent(ScopeLegacyNavigation);
const findSmallScreenDrawerNavigation = () => wrapper.findComponent(SmallScreenDrawerNavigation);
const findScopeSidebarNavigation = () => wrapper.findComponent(ScopeSidebarNavigation);
@ -82,6 +85,8 @@ describe('GlobalSearchSidebar', () => {
${'blobs'} | ${findBlobsFilters} | ${SEARCH_TYPE_ZOEKT} | ${false}
${'notes'} | ${findNotesFilters} | ${SEARCH_TYPE_BASIC} | ${false}
${'notes'} | ${findNotesFilters} | ${SEARCH_TYPE_ADVANCED} | ${true}
${'commits'} | ${findCommitsFilters} | ${SEARCH_TYPE_BASIC} | ${false}
${'commits'} | ${findCommitsFilters} | ${SEARCH_TYPE_ADVANCED} | ${true}
`('with sidebar $scope scope:', ({ scope, filter, searchType, isShown }) => {
beforeEach(() => {
getterSpies.currentScope = jest.fn(() => scope);

View File

@ -0,0 +1,28 @@
import { shallowMount } from '@vue/test-utils';
import CommitsFilters from '~/search/sidebar/components/projects_filters.vue';
import ArchivedFilter from '~/search/sidebar/components/archived_filter/index.vue';
import FiltersTemplate from '~/search/sidebar/components/filters_template.vue';
describe('GlobalSearch CommitsFilters', () => {
let wrapper;
const findArchivedFilter = () => wrapper.findComponent(ArchivedFilter);
const findFiltersTemplate = () => wrapper.findComponent(FiltersTemplate);
const createComponent = () => {
wrapper = shallowMount(CommitsFilters);
};
describe('Renders correctly', () => {
beforeEach(() => {
createComponent();
});
it('renders ArchivedFilter', () => {
expect(findArchivedFilter().exists()).toBe(true);
});
it('renders FiltersTemplate', () => {
expect(findFiltersTemplate().exists()).toBe(true);
});
});
});

View File

@ -18,16 +18,6 @@ RSpec.describe ProjectAuthorization, feature_category: :groups_and_projects do
it 'sets is_unique' do
expect { project_auth.save! }.to change { project_auth.is_unique }.to(true)
end
context 'with feature disabled' do
before do
stub_feature_flags(write_project_authorizations_is_unique: false)
end
it 'does not set is_unique' do
expect { project_auth.save! }.not_to change { project_auth.is_unique }.from(nil)
end
end
end
describe 'unique user, project authorizations' do

View File

@ -110,18 +110,6 @@ RSpec.describe ProjectAuthorizations::Changes, feature_category: :groups_and_pro
expect(user.project_authorizations.pluck(:is_unique)).to all(be(true))
end
context 'with feature disabled' do
before do
stub_feature_flags(write_project_authorizations_is_unique: false)
end
it 'does not write is_unique' do
apply_project_authorization_changes
expect(user.project_authorizations.pluck(:is_unique)).to all(be(nil))
end
end
it_behaves_like 'logs the detail', batch_size: 2
it_behaves_like 'publishes AuthorizationsChangedEvent'

View File

@ -35,20 +35,6 @@ RSpec.describe Ci::PipelineCreation::CancelRedundantPipelinesService, feature_ca
expect(build_statuses(pipeline)).to contain_exactly('pending')
expect(build_statuses(old_pipeline)).to contain_exactly('pending')
end
context 'with lower_interval_for_canceling_redundant_pipelines disabled' do
before do
stub_feature_flags(lower_interval_for_canceling_redundant_pipelines: false)
end
it 'cancels pipelines created more than 3 days ago' do
execute
expect(build_statuses(prev_pipeline)).to contain_exactly('canceled', 'success', 'canceled')
expect(build_statuses(pipeline)).to contain_exactly('pending')
expect(build_statuses(old_pipeline)).to contain_exactly('canceled')
end
end
end
end

View File

@ -98,8 +98,8 @@ module Tooling
\.gitlab/ci/frontend\.gitlab-ci\.yml
)\z}x => %i[frontend tooling],
%r{\A((ee|jh)/)?db/(geo/)?(migrate|post_migrate)/} => [:database],
%r{\A((ee|jh)/)?db/(?!fixtures)[^/]+} => [:database],
%r{\A((ee|jh)/)?db/(geo/)?(?!click_house|fixtures)[^/]+} => [:database],
%r{\A((ee|jh)/)?db/[^/]+\z} => [:database], # db/ root files
%r{\A((ee|jh)/)?lib/gitlab/(database|background_migration|sql)(/|\.rb)} => [:database, :backend],
%r{\A(app/services/authorized_project_update/find_records_due_for_refresh_service)(/|\.rb)} => [:database, :backend],
%r{\A(app/models/project_authorization|app/services/users/refresh_authorized_projects_service)(/|\.rb)} => [:database, :backend],