Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-11-05 15:24:02 +00:00
parent 28cff3de2b
commit b17372c8e7
109 changed files with 1378 additions and 1186 deletions

View File

@ -233,7 +233,6 @@ Rails/Pluck:
- 'spec/requests/groups/autocomplete_sources_spec.rb'
- 'spec/requests/groups/milestones_controller_spec.rb'
- 'spec/requests/lfs_http_spec.rb'
- 'spec/serializers/ci/dag_pipeline_entity_spec.rb'
- 'spec/serializers/ci/pipeline_entity_spec.rb'
- 'spec/serializers/diff_file_entity_spec.rb'
- 'spec/serializers/stage_entity_spec.rb'

View File

@ -414,11 +414,6 @@ RSpec/FactoryBot/AvoidCreate:
- 'spec/serializers/build_action_entity_spec.rb'
- 'spec/serializers/build_artifact_entity_spec.rb'
- 'spec/serializers/build_details_entity_spec.rb'
- 'spec/serializers/ci/dag_job_entity_spec.rb'
- 'spec/serializers/ci/dag_job_group_entity_spec.rb'
- 'spec/serializers/ci/dag_pipeline_entity_spec.rb'
- 'spec/serializers/ci/dag_pipeline_serializer_spec.rb'
- 'spec/serializers/ci/dag_stage_entity_spec.rb'
- 'spec/serializers/ci/downloadable_artifact_entity_spec.rb'
- 'spec/serializers/ci/downloadable_artifact_serializer_spec.rb'
- 'spec/serializers/ci/job_entity_spec.rb'

View File

@ -3863,11 +3863,6 @@ RSpec/FeatureCategory:
- 'spec/serializers/build_details_entity_spec.rb'
- 'spec/serializers/build_trace_entity_spec.rb'
- 'spec/serializers/ci/codequality_mr_diff_report_serializer_spec.rb'
- 'spec/serializers/ci/dag_job_entity_spec.rb'
- 'spec/serializers/ci/dag_job_group_entity_spec.rb'
- 'spec/serializers/ci/dag_pipeline_entity_spec.rb'
- 'spec/serializers/ci/dag_pipeline_serializer_spec.rb'
- 'spec/serializers/ci/dag_stage_entity_spec.rb'
- 'spec/serializers/ci/daily_build_group_report_result_entity_spec.rb'
- 'spec/serializers/ci/daily_build_group_report_result_serializer_spec.rb'
- 'spec/serializers/ci/downloadable_artifact_entity_spec.rb'

View File

@ -2817,11 +2817,6 @@ RSpec/NamedSubject:
- 'spec/serializers/build_details_entity_spec.rb'
- 'spec/serializers/build_trace_entity_spec.rb'
- 'spec/serializers/ci/codequality_mr_diff_report_serializer_spec.rb'
- 'spec/serializers/ci/dag_job_entity_spec.rb'
- 'spec/serializers/ci/dag_job_group_entity_spec.rb'
- 'spec/serializers/ci/dag_pipeline_entity_spec.rb'
- 'spec/serializers/ci/dag_pipeline_serializer_spec.rb'
- 'spec/serializers/ci/dag_stage_entity_spec.rb'
- 'spec/serializers/ci/downloadable_artifact_entity_spec.rb'
- 'spec/serializers/ci/downloadable_artifact_serializer_spec.rb'
- 'spec/serializers/ci/group_variable_entity_spec.rb'

View File

@ -725,10 +725,6 @@ RSpec/VerifiedDoubles:
- 'spec/serializers/build_action_entity_spec.rb'
- 'spec/serializers/build_details_entity_spec.rb'
- 'spec/serializers/build_trace_entity_spec.rb'
- 'spec/serializers/ci/dag_job_entity_spec.rb'
- 'spec/serializers/ci/dag_job_group_entity_spec.rb'
- 'spec/serializers/ci/dag_pipeline_entity_spec.rb'
- 'spec/serializers/ci/dag_stage_entity_spec.rb'
- 'spec/serializers/ci/daily_build_group_report_result_entity_spec.rb'
- 'spec/serializers/ci/daily_build_group_report_result_serializer_spec.rb'
- 'spec/serializers/ci/job_entity_spec.rb'

View File

@ -489,9 +489,6 @@ Style/InlineDisableAnnotation:
- 'app/serializers/analytics/cycle_analytics/value_stream_entity.rb'
- 'app/serializers/analytics_build_entity.rb'
- 'app/serializers/analytics_issue_entity.rb'
- 'app/serializers/ci/dag_job_entity.rb'
- 'app/serializers/ci/dag_pipeline_entity.rb'
- 'app/serializers/ci/job_entity.rb'
- 'app/serializers/cluster_entity.rb'
- 'app/serializers/diffs_metadata_entity.rb'
- 'app/serializers/environment_serializer.rb'

View File

@ -1,2 +0,0 @@
// /dag is an alias for show
import '../show/index';

View File

@ -24,11 +24,6 @@ export default {
type: Object,
required: true,
},
fadeDoneTodo: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
isDone() {
@ -40,9 +35,6 @@ export default {
targetUrl() {
return this.todo.targetUrl;
},
fadeTodo() {
return this.fadeDoneTodo && this.isDone;
},
trackingLabel() {
return this.todo.targetType ?? 'UNKNOWN';
},
@ -53,7 +45,6 @@ export default {
<template>
<li
class="gl-border-t gl-border-b gl-relative -gl-mt-px gl-block gl-px-5 gl-py-3 hover:gl-z-1 hover:gl-cursor-pointer hover:gl-border-blue-200 hover:gl-bg-blue-50"
:class="{ 'gl-border-gray-50 gl-bg-gray-10': fadeTodo }"
>
<gl-link
:href="targetUrl"
@ -63,16 +54,18 @@ export default {
>
<div
class="gl-w-64 gl-flex-grow-2 gl-self-center gl-overflow-hidden gl-overflow-x-auto sm:gl-w-auto"
:class="{ 'gl-opacity-5': fadeTodo }"
>
<todo-item-title :todo="todo" />
<todo-item-body :todo="todo" :current-user-id="currentUserId" />
</div>
<todo-item-actions :todo="todo" class="sm:gl-order-3" />
<todo-item-actions
:todo="todo"
class="sm:gl-order-3"
@change="(id, markedAsDone) => $emit('change', id, markedAsDone)"
/>
<todo-item-timestamp
:todo="todo"
class="gl-w-full gl-whitespace-nowrap gl-px-2 sm:gl-w-auto"
:class="{ 'gl-opacity-5': fadeTodo }"
/>
</gl-link>
</li>

View File

@ -3,7 +3,12 @@ import { GlButton, GlTooltipDirective } from '@gitlab/ui';
import { reportToSentry } from '~/ci/utils';
import { s__ } from '~/locale';
import Tracking from '~/tracking';
import { INSTRUMENT_TODO_ITEM_CLICK, TODO_STATE_DONE, TODO_STATE_PENDING } from '../constants';
import {
INSTRUMENT_TODO_ITEM_CLICK,
TAB_ALL,
TODO_STATE_DONE,
TODO_STATE_PENDING,
} from '../constants';
import markAsDoneMutation from './mutations/mark_as_done.mutation.graphql';
import markAsPendingMutation from './mutations/mark_as_pending.mutation.graphql';
@ -15,6 +20,7 @@ export default {
GlTooltip: GlTooltipDirective,
},
mixins: [Tracking.mixin()],
inject: ['currentTab'],
props: {
todo: {
type: Object,
@ -33,6 +39,14 @@ export default {
isPending() {
return this.todo.state === TODO_STATE_PENDING;
},
tooltipTitle() {
// Setting this to null while loading, combined with keeping the
// loading state till the item gets removed, prevents the tooltip
// text changing with the item state before the item gets removed.
if (this.isLoading) return null;
return this.isDone ? this.$options.i18n.markAsPending : this.$options.i18n.markAsDone;
},
},
methods: {
showMarkAsDoneError() {
@ -75,12 +89,22 @@ export default {
if (data.errors?.length > 0) {
reportToSentry(this.$options.name, new Error(data.errors.join(', ')));
showError();
} else {
this.$emit('change', this.todo.id, this.isDone);
}
} catch (failure) {
reportToSentry(this.$options.name, failure);
showError();
} finally {
this.isLoading = false;
} finally {
// Only stop loading spinner when on "All" tab.
// On the other tabs (Pending/Done) we want the loading to continue
// until the todos query finished, removing this item from the list.
// This way we hide the state change, which would otherwise update
// the button's icon before it gets removed.
if (this.currentTab === TAB_ALL) {
this.isLoading = false;
}
}
},
},
@ -97,7 +121,7 @@ export default {
:icon="isDone ? 'redo' : 'check'"
:loading="isLoading"
:aria-label="isDone ? $options.i18n.markAsPending : $options.i18n.markAsDone"
:title="isDone ? $options.i18n.markAsPending : $options.i18n.markAsDone"
:title="tooltipTitle"
@click.prevent="toggleStatus"
/>
</template>

View File

@ -1,4 +1,5 @@
<script>
import { computed } from 'vue';
import { GlLoadingIcon, GlKeysetPagination, GlLink, GlBadge, GlTab, GlTabs } from '@gitlab/ui';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import { createAlert } from '~/alert';
@ -11,6 +12,8 @@ import {
} from '~/todos/constants';
import getTodosQuery from './queries/get_todos.query.graphql';
import getPendingTodosCount from './queries/get_pending_todos_count.query.graphql';
import markAsDoneMutation from './mutations/mark_as_done.mutation.graphql';
import markAsPendingMutation from './mutations/mark_as_pending.mutation.graphql';
import TodoItem from './todo_item.vue';
import TodosEmptyState from './todos_empty_state.vue';
import TodosFilterBar, { SORT_OPTIONS } from './todos_filter_bar.vue';
@ -32,6 +35,11 @@ export default {
TodosMarkAllDoneButton,
},
mixins: [Tracking.mixin()],
provide() {
return {
currentTab: computed(() => this.currentTab),
};
},
data() {
return {
cursor: {
@ -54,11 +62,13 @@ export default {
sort: `${SORT_OPTIONS[0].value}_DESC`,
},
alert: null,
showSpinnerWhileLoading: true,
};
},
apollo: {
todos: {
query: getTodosQuery,
fetchPolicy: 'cache-and-network',
variables() {
return {
state: this.statusByTab,
@ -107,9 +117,6 @@ export default {
showMarkAllAsDone() {
return this.currentTab === 0 && !this.showEmptyState;
},
fadeDoneTodo() {
return this.currentTab === 0;
},
},
methods: {
nextPage(item) {
@ -144,9 +151,34 @@ export default {
this.alert?.dismiss();
this.queryFilterValues = { ...data };
},
async handleItemChanged(id, markedAsDone) {
await this.updateAllQueries(false);
this.showUndoToast(id, markedAsDone);
},
showUndoToast(todoId, markedAsDone) {
const message = markedAsDone ? s__('Todos|Marked as done') : s__('Todos|Marked as undone');
const mutation = markedAsDone ? markAsPendingMutation : markAsDoneMutation;
const { hide } = this.$toast.show(message, {
action: {
text: s__('Todos|Undo'),
onClick: async () => {
hide();
await this.$apollo.mutate({ mutation, variables: { todoId } });
this.updateAllQueries(false);
},
},
});
},
updateCounts() {
this.$apollo.queries.pendingTodosCount.refetch();
},
async updateAllQueries(showLoading = true) {
this.showSpinnerWhileLoading = showLoading;
this.updateCounts();
await this.$apollo.queries.todos.refetch();
this.showSpinnerWhileLoading = true;
},
},
};
</script>
@ -184,15 +216,17 @@ export default {
<div>
<div class="gl-flex gl-flex-col">
<gl-loading-icon v-if="isLoading" size="lg" class="gl-mt-5" />
<gl-loading-icon v-if="isLoading && showSpinnerWhileLoading" size="lg" class="gl-mt-5" />
<ul v-else class="gl-m-0 gl-border-collapse gl-list-none gl-p-0">
<todo-item
v-for="todo in todos"
:key="todo.id"
:todo="todo"
:current-user-id="currentUserId"
:fade-done-todo="fadeDoneTodo"
/>
<transition-group name="todos">
<todo-item
v-for="todo in todos"
:key="todo.id"
:todo="todo"
:current-user-id="currentUserId"
@change="handleItemChanged"
/>
</transition-group>
</ul>
<todos-empty-state v-if="showEmptyState" :is-filtered="isFiltered" />
@ -214,3 +248,17 @@ export default {
</div>
</div>
</template>
<style>
.todos-leave-active {
transition: transform 0.15s ease-out;
position: absolute;
}
.todos-leave-to {
opacity: 0;
transform: translateY(-100px);
}
.todos-move {
transition: transform 0.15s ease-out;
}
</style>

View File

@ -36,6 +36,7 @@ export const TODO_EMPTY_TITLE_POOL = [
];
export const STATUS_BY_TAB = [['pending'], ['done'], ['pending', 'done']];
export const TAB_ALL = 2;
/**
* Instrumentation

View File

@ -9,7 +9,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
before_action :authorize_read_group!, only: :index
before_action :find_todos, only: [:index, :destroy_all]
feature_category :team_planning
feature_category :notifications
urgency :low
def index

View File

@ -7,7 +7,7 @@ class Projects::PipelinesController < Projects::ApplicationController
urgency :low, [
:index, :new, :builds, :show, :failures, :create,
:stage, :retry, :dag, :cancel, :test_report,
:stage, :retry, :cancel, :test_report,
:charts, :destroy, :status, :manual_variables
]
@ -27,7 +27,7 @@ class Projects::PipelinesController < Projects::ApplicationController
before_action :authorize_cancel_pipeline!, only: [:cancel]
before_action :ensure_pipeline, only: [:show, :downloadable_artifacts]
before_action :reject_if_build_artifacts_size_refreshing!, only: [:destroy]
before_action only: [:show, :dag, :builds, :failures, :test_report, :manual_variables] do
before_action only: [:show, :builds, :failures, :test_report, :manual_variables] do
push_frontend_feature_flag(:ci_show_manual_variables_in_pipeline, project)
end
@ -54,7 +54,7 @@ class Projects::PipelinesController < Projects::ApplicationController
feature_category :continuous_integration, [
:charts, :show, :stage, :cancel, :retry,
:builds, :dag, :failures, :status,
:builds, :failures, :status,
:index, :new, :destroy, :manual_variables
]
feature_category :pipeline_composition, [:create]
@ -144,19 +144,6 @@ class Projects::PipelinesController < Projects::ApplicationController
render_show
end
def dag
respond_to do |format|
format.html do
render_show
end
format.json do
render json: Ci::DagPipelineSerializer
.new(project: @project, current_user: @current_user)
.represent(@pipeline)
end
end
end
def failures
if @pipeline.failed_builds.present?
render_show

View File

@ -6,7 +6,7 @@ class Projects::TodosController < Projects::ApplicationController
before_action :authenticate_user!, only: [:create]
feature_category :team_planning
feature_category :notifications
urgency :low
private

View File

@ -35,30 +35,21 @@ module Resolvers
return unless runner.project_type?
BatchLoader::GraphQL.for(runner.id).batch do |runner_ids, loader|
# rubocop: disable CodeReuse/ActiveRecord
runner_and_projects_with_row_number =
::Ci::RunnerProject
.where(runner_id: runner_ids)
.select('id, runner_id, project_id, ROW_NUMBER() OVER (PARTITION BY runner_id ORDER BY id ASC)')
runner_and_owner_projects =
::Ci::RunnerProject
.select(:id, :runner_id, :project_id)
.from("(#{runner_and_projects_with_row_number.to_sql}) temp WHERE row_number = 1")
owner_project_id_by_runner_id =
runner_and_owner_projects
.group_by(&:runner_id)
.transform_values { |runner_projects| runner_projects.first.project_id }
project_ids = owner_project_id_by_runner_id.values.uniq
# rubocop: disable CodeReuse/ActiveRecord -- this runs on a limited number of records
runner_id_to_owner_id =
::Ci::Runner.project_type.id_in(runner_ids)
.pluck(:id, :sharding_key_id)
.to_h
# rubocop: enable CodeReuse/ActiveRecord
projects = apply_lookahead(Project.id_in(project_ids))
projects = apply_lookahead(Project.id_in(runner_id_to_owner_id.values))
Preloaders::ProjectPolicyPreloader.new(projects, current_user).execute
projects_by_id = projects.index_by(&:id)
runner_ids.each do |runner_id|
owner_project_id = owner_project_id_by_runner_id[runner_id]
owner_project_id = runner_id_to_owner_id[runner_id]
loader.call(runner_id, projects_by_id[owner_project_id])
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
end

View File

@ -48,15 +48,10 @@ module Ci
request
end
def pipeline_creating_for_merge_request?(merge_request, delete_if_all_complete: false)
def pipeline_creating_for_merge_request?(merge_request)
key = merge_request_key(merge_request)
requests, _del_result = Gitlab::Redis::SharedState.with do |redis|
redis.multi do |transaction|
transaction.hvals(key)
transaction.del(key) if delete_if_all_complete
end
end
requests = Gitlab::Redis::SharedState.with { |redis| redis.hvals(key) }
return false unless requests.present?

View File

@ -331,19 +331,18 @@ module Ci
end
def runner_matcher
strong_memoize(:runner_matcher) do
Gitlab::Ci::Matching::RunnerMatcher.new({
runner_ids: [id],
runner_type: runner_type,
public_projects_minutes_cost_factor: public_projects_minutes_cost_factor,
private_projects_minutes_cost_factor: private_projects_minutes_cost_factor,
run_untagged: run_untagged,
access_level: access_level,
tag_list: tag_list,
allowed_plan_ids: allowed_plan_ids
})
end
Gitlab::Ci::Matching::RunnerMatcher.new({
runner_ids: [id],
runner_type: runner_type,
public_projects_minutes_cost_factor: public_projects_minutes_cost_factor,
private_projects_minutes_cost_factor: private_projects_minutes_cost_factor,
run_untagged: run_untagged,
access_level: access_level,
tag_list: tag_list,
allowed_plan_ids: allowed_plan_ids
})
end
strong_memoize_attr :runner_matcher
def assign_to(project, current_user = nil)
if instance_type?
@ -354,7 +353,11 @@ module Ci
begin
transaction do
self.sharding_key_id = project.id if self.runner_projects.empty?
if self.runner_projects.empty?
self.sharding_key_id = project.id
self.clear_memoization(:owner)
end
self.runner_projects << ::Ci::RunnerProject.new(project: project, runner: self)
self.save!
end
@ -384,14 +387,20 @@ module Ci
end
end
def owner_project
return unless project_type?
runner_projects.order(:id).first&.project
def owner
case runner_type
when 'instance_type'
::User.find_by_id(creator_id)
when 'group_type'
::Group.find_by_id(sharding_key_id)
when 'project_type'
::Project.find_by_id(sharding_key_id)
end
end
strong_memoize_attr :owner
def belongs_to_one_project?
runner_projects.count == 1
runner_projects.limit(2).count(:all) == 1
end
def belongs_to_more_than_one_project?
@ -497,10 +506,9 @@ module Ci
end
def namespace_ids
strong_memoize(:namespace_ids) do
runner_namespaces.pluck(:namespace_id).compact
end
runner_namespaces.pluck(:namespace_id).compact
end
strong_memoize_attr :namespace_ids
def compute_token_expiration
case runner_type

View File

@ -2440,7 +2440,7 @@ class MergeRequest < ApplicationRecord
def pipeline_creating?
return false unless Feature.enabled?(:ci_redis_pipeline_creations, project)
Ci::PipelineCreation::Requests.pipeline_creating_for_merge_request?(self, delete_if_all_complete: true)
Ci::PipelineCreation::Requests.pipeline_creating_for_merge_request?(self)
end
def merge_base_pipelines

View File

@ -1,12 +0,0 @@
# frozen_string_literal: true
module Ci
class DagJobEntity < Grape::Entity
expose :name
expose :scheduling_type
expose :needs, if: ->(job, _) { job.scheduling_type_dag? } do |job|
job.needs.pluck(:name) # rubocop: disable CodeReuse/ActiveRecord
end
end
end

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
module Ci
class DagJobGroupEntity < Grape::Entity
expose :name
expose :size
expose :jobs, with: Ci::DagJobEntity
end
end

View File

@ -1,20 +0,0 @@
# frozen_string_literal: true
module Ci
class DagPipelineEntity < Grape::Entity
expose :stages_with_preloads, as: :stages, using: Ci::DagStageEntity
private
def stages_with_preloads
object.stages.preload(preloaded_relations) # rubocop: disable CodeReuse/ActiveRecord
end
def preloaded_relations
[
:project,
{ latest_statuses: :needs }
]
end
end
end

View File

@ -1,7 +0,0 @@
# frozen_string_literal: true
module Ci
class DagPipelineSerializer < BaseSerializer
entity Ci::DagPipelineEntity
end
end

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
module Ci
class DagStageEntity < Grape::Entity
expose :name
expose :groups, with: Ci::DagJobGroupEntity
end
end

View File

@ -73,7 +73,9 @@ module Ci
end
def path_to(route, job, params = {})
send("#{route}_path", job.project.namespace, job.project, job, params) # rubocop:disable GitlabSecurity/PublicSend
# rubocop:disable GitlabSecurity/PublicSend -- needs send
send("#{route}_path", job.project.namespace, job.project, job, params)
# rubocop:enable GitlabSecurity/PublicSend
end
def job_path(job)

View File

@ -45,7 +45,7 @@ module Ci
reason: :not_authorized_to_add_runner_in_project)
end
if runner.owner_project && project.organization_id != runner.owner_project.organization_id
if runner.owner && project.organization_id != runner.owner.organization_id
return ServiceResponse.error(message: _('runner can only be assigned to projects in the same organization'),
reason: :project_not_in_same_organization)
end

View File

@ -26,13 +26,11 @@ module Ci
private
def set_associated_projects
new_project_ids = [runner.owner_project&.id].compact + project_ids
new_project_ids = [runner.owner&.id].compact + project_ids
response = ServiceResponse.success
runner.transaction do
# rubocop:disable CodeReuse/ActiveRecord
current_project_ids = runner.projects.ids
# rubocop:enable CodeReuse/ActiveRecord
current_project_ids = runner.project_ids # rubocop:disable CodeReuse/ActiveRecord -- reasonable use
response = associate_new_projects(new_project_ids, current_project_ids)
response = disassociate_old_projects(new_project_ids, current_project_ids) if response.success?

View File

@ -33,9 +33,9 @@ module Ci
kwargs = { user: current_user }
case runner.runner_type
when 'group_type'
kwargs[:namespace] = runner.groups.first
kwargs[:namespace] = runner.owner
when 'project_type'
kwargs[:project] = runner.owner_project
kwargs[:project] = runner.owner
end
track_internal_event(

View File

@ -26,6 +26,8 @@ module MergeRequests
invalidate_cache_counts(merge_request, users: merge_request.assignees)
end
invalidate_cache_counts(merge_request, users: old_assignees)
execute_assignees_hooks(merge_request, old_assignees) if options['execute_hooks']
end

View File

@ -2318,7 +2318,7 @@
:tags: []
- :name: todos_destroyer:todos_destroyer_confidential_issue
:worker_name: TodosDestroyer::ConfidentialIssueWorker
:feature_category: :team_planning
:feature_category: :notifications
:has_external_dependencies: false
:urgency: :low
:resource_boundary: :unknown
@ -2327,7 +2327,7 @@
:tags: []
- :name: todos_destroyer:todos_destroyer_destroyed_designs
:worker_name: TodosDestroyer::DestroyedDesignsWorker
:feature_category: :team_planning
:feature_category: :notifications
:has_external_dependencies: false
:urgency: :low
:resource_boundary: :unknown
@ -2336,7 +2336,7 @@
:tags: []
- :name: todos_destroyer:todos_destroyer_destroyed_issuable
:worker_name: TodosDestroyer::DestroyedIssuableWorker
:feature_category: :team_planning
:feature_category: :notifications
:has_external_dependencies: false
:urgency: :low
:resource_boundary: :unknown
@ -2345,7 +2345,7 @@
:tags: []
- :name: todos_destroyer:todos_destroyer_entity_leave
:worker_name: TodosDestroyer::EntityLeaveWorker
:feature_category: :team_planning
:feature_category: :notifications
:has_external_dependencies: false
:urgency: :low
:resource_boundary: :unknown
@ -2354,7 +2354,7 @@
:tags: []
- :name: todos_destroyer:todos_destroyer_group_private
:worker_name: TodosDestroyer::GroupPrivateWorker
:feature_category: :team_planning
:feature_category: :notifications
:has_external_dependencies: false
:urgency: :low
:resource_boundary: :unknown
@ -2363,7 +2363,7 @@
:tags: []
- :name: todos_destroyer:todos_destroyer_private_features
:worker_name: TodosDestroyer::PrivateFeaturesWorker
:feature_category: :team_planning
:feature_category: :notifications
:has_external_dependencies: false
:urgency: :low
:resource_boundary: :unknown
@ -2372,7 +2372,7 @@
:tags: []
- :name: todos_destroyer:todos_destroyer_project_private
:worker_name: TodosDestroyer::ProjectPrivateWorker
:feature_category: :team_planning
:feature_category: :notifications
:has_external_dependencies: false
:urgency: :low
:resource_boundary: :unknown

View File

@ -8,6 +8,6 @@ module TodosDestroyerQueue
included do
queue_namespace :todos_destroyer
feature_category :team_planning
feature_category :notifications
end
end

View File

@ -6,7 +6,7 @@ column: default_project_creation
db_type: integer
default: '2'
description: 'Default project creation protection. Can take: `0` _(No one)_, `1` _(Maintainers)_,
`2` _(Developers + Maintainers)_ or `3` _(Administrators)_'
`2` _(Developers + Maintainers)_, or `3` _(Administrators)_.'
encrypted: false
gitlab_com_different_than_default: false
jihu: false

View File

@ -5,7 +5,8 @@ clusterwide: false
column: elasticsearch_retry_on_failure
db_type: integer
default: '0'
description: Maximum number of possible retries for Elasticsearch search requests. Premium and Ultimate only.
description: Maximum number of possible retries for Elasticsearch search requests.
Premium and Ultimate only.
encrypted: false
gitlab_com_different_than_default: false
jihu: false

View File

@ -5,7 +5,7 @@ clusterwide: true
column: identity_verification_settings
db_type: jsonb
default: "'{}'::jsonb"
description: 'Configuration settings related to identity verification'
description:
encrypted: false
gitlab_com_different_than_default: true
jihu: false

View File

@ -8,7 +8,9 @@ default:
description: Maximum allowable lifetime for access tokens in days. When left blank,
default value of 365 is applied. When set, value must be 365 or less. When changed,
existing access tokens with an expiration date beyond the maximum allowable lifetime
are revoked. Self-managed, Ultimate only.
are revoked. Self-managed, Ultimate only. In GitLab 17.6 or later, the maximum lifetime
limit can be [extended to 400 days](https://gitlab.com/gitlab-org/gitlab/-/issues/461901)
by enabling a [feature flag](../administration/feature_flags.md) named `buffered_token_expiration_limit`.
encrypted: false
gitlab_com_different_than_default: false
jihu: false

View File

@ -6,7 +6,9 @@ column: max_ssh_key_lifetime
db_type: integer
default:
description: Maximum allowable lifetime for SSH keys in days. Self-managed, Ultimate
only.
only. In GitLab 17.6 or later, the maximum lifetime limit can be [extended to 400
days](https://gitlab.com/gitlab-org/gitlab/-/issues/461901) by enabling a [feature
flag](../administration/feature_flags.md) named `buffered_token_expiration_limit`.
encrypted: false
gitlab_com_different_than_default: false
jihu: false

View File

@ -5,7 +5,7 @@ clusterwide: false
column: sign_in_restrictions
db_type: jsonb
default: "'{}'::jsonb"
description: "Settings related to sign-in restrictions"
description:
encrypted: false
gitlab_com_different_than_default: true
jihu: false

View File

@ -5,10 +5,7 @@ clusterwide: false
column: transactional_emails
db_type: jsonb
default: "'{}'::jsonb"
description: >
Settings for transactional emails.
[Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/168245) in
GitLab 17.6 to support options for group and project access token expiry.
description:
encrypted: false
gitlab_com_different_than_default: false
jihu: false

View File

@ -0,0 +1,9 @@
---
name: expand_nested_variables_in_job_rules_exists_and_changes
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/327780
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/166779
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/483115
milestone: '17.6'
group: group::pipeline authoring
type: gitlab_com_derisk
default_enabled: false

View File

@ -14,7 +14,6 @@ resources :pipelines, only: [:index, :new, :create, :show, :destroy] do
post :cancel
post :retry
get :builds
get :dag
get :failures
get :status
get :test_report

View File

@ -10,3 +10,8 @@
[GitLab Runner documentation](https://docs.gitlab.com/runner/)
stage: verify
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/387937
window: "1"
impact: low
scope: [instance, group, project]
resolution_role: admin
manual_task: false

View File

@ -1,13 +0,0 @@
- title: "List container registry repository tags API endpoint pagination"
announcement_milestone: "16.10"
removal_milestone: "18.0"
breaking_change: true
reporter: trizzi
stage: Package
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/432470
body: |
You can use the container registry REST API to [get a list of registry repository tags](https://docs.gitlab.com/ee/api/container_registry.html#list-registry-repository-tags). We plan to improve this endpoint, adding more metadata and new features like improved sorting and filtering.
While offset-based pagination was already available for this endpoint, keyset-based pagination was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/432470) in GitLab 16.10 for GitLab.com only. This is now the preferred pagination method.
Offset-based pagination for the [List registry repository tags](https://docs.gitlab.com/ee/api/container_registry.html#list-registry-repository-tags) endpoint is deprecated in GitLab 16.10 and will be removed in 18.0. Instead, use the keyset-based pagination.

View File

@ -3,7 +3,7 @@ table_name: todos
classes:
- Todo
feature_categories:
- team_planning
- notifications
description: >-
An action required or notification of action taken for a user on a target object, generated by various actions within the
GitLab application

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
class AddMetadataToZoektIndices < Gitlab::Database::Migration[2.2]
enable_lock_retries!
milestone '17.6'
def change
add_column :zoekt_indices, :metadata, :jsonb, default: {}, null: false
end
end

View File

@ -0,0 +1 @@
7bc71fb040af525f9592177bfe35225a44d84b1a85d8c5bc669664c00303bafc

View File

@ -22005,7 +22005,8 @@ CREATE TABLE zoekt_indices (
zoekt_replica_id bigint,
reserved_storage_bytes bigint DEFAULT '10737418240'::bigint,
used_storage_bytes bigint DEFAULT 0 NOT NULL,
watermark_level smallint DEFAULT 0 NOT NULL
watermark_level smallint DEFAULT 0 NOT NULL,
metadata jsonb DEFAULT '{}'::jsonb NOT NULL
);
CREATE SEQUENCE zoekt_indices_id_seq

View File

@ -47,14 +47,18 @@ supporting custom domains a secondary IP is not needed.
## Prerequisites
Before proceeding with the Pages configuration, you must:
This section describes the prerequisites for configuring GitLab Pages.
### Wildcard domains
Before configuring Pages for wildcard domains, you must:
1. Have a domain for Pages that is not a subdomain of your GitLab instance domain.
| GitLab domain | Pages domain | Does it work? |
| :---: | :---: | :---: |
| `example.com` | `example.io` | **{check-circle}** Yes |
| `example.com` | `pages.example.com` | **{dotted-circle}** No |
| GitLab domain | Pages domain | Does it work? |
| -------------------- | ------------------- | ------------- |
| `example.com` | `example.io` | **{check-circle}** Yes |
| `example.com` | `pages.example.com` | **{dotted-circle}** No |
| `gitlab.example.com` | `pages.example.com` | **{check-circle}** Yes |
1. Configure a **wildcard DNS record**.
@ -64,6 +68,24 @@ Before proceeding with the Pages configuration, you must:
so that your users don't have to bring their own.
1. For custom domains, have a **secondary IP**.
### Single-domain sites
Before configuring Pages for single-domain sites, you must:
1. Have a domain for Pages that is not a subdomain of your GitLab instance domain.
| GitLab domain | Pages domain | Supported |
| -------------------- | ------------------- | ------------- |
| `example.com` | `example.io` | **{check-circle}** Yes |
| `example.com` | `pages.example.com` | **{dotted-circle}** No |
| `gitlab.example.com` | `pages.example.com` | **{check-circle}** Yes |
1. Configure a **DNS record**.
1. Optional. If you decide to serve Pages under HTTPS, have a **TLS certificate** for that domain.
1. Optional but recommended. Enable [instance runners](../../ci/runners/index.md)
so that your users don't have to bring their own.
1. For custom domains, have a **secondary IP**.
NOTE:
If your GitLab instance and the Pages daemon are deployed in a private network or behind a firewall, your GitLab Pages websites are only accessible to devices/users that have access to the private network.
@ -77,8 +99,8 @@ Suffix List prevents browsers from accepting
[supercookies](https://en.wikipedia.org/wiki/HTTP_cookie#Supercookie),
among other things.
Follow [these instructions](https://publicsuffix.org/submit/) to submit your
GitLab Pages subdomain. For instance, if your domain is `example.io`, you should
To submit your GitLab Pages subdomain, follow [Submit amendments to the Public Suffix List](https://publicsuffix.org/submit/).
For example, if your domain is `example.io`, you should
request that `example.io` is added to the Public Suffix List. GitLab.com
added `gitlab.io` [in 2016](https://gitlab.com/gitlab-com/gl-infra/reliability/-/issues/230).
@ -97,14 +119,14 @@ Where `example.io` is the domain GitLab Pages is served from,
`192.0.2.1` is the IPv4 address of your GitLab instance, and `2001:db8::1` is the
IPv6 address. If you don't have IPv6, you can omit the `AAAA` record.
#### For namespace in URL path, without wildcard DNS
#### DNS configuration for single-domain sites
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/17584) as an [experiment](../../policy/experiment-beta-support.md) in GitLab 16.7.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148621) to [beta](../../policy/experiment-beta-support.md) in GitLab 16.11.
> - [Changed](https://gitlab.com/gitlab-org/gitlab-pages/-/issues/1111) implementation from NGINX to the GitLab Pages codebase in GitLab 17.2.
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/483365) in GitLab 17.4.
If you need support for namespace in the URL path to remove the requirement for wildcard DNS:
To configure GitLab Pages DNS for single-domain sites without wildcard DNS:
1. Enable the GitLab Pages flag for this feature by adding
`gitlab_pages["namespace_in_path"] = true` to `/etc/gitlab/gitlab.rb`.
@ -160,17 +182,18 @@ advanced one.
### Wildcard domains
The following configuration is the minimum setup to use GitLab Pages.
It is the foundation for all other setups described here.
In this configuration:
- NGINX proxies all requests to the GitLab Pages daemon.
- The GitLab Pages daemon doesn't listen directly to the public internet.
Prerequisites:
- [Wildcard DNS setup](#dns-configuration)
---
URL scheme: `http://<namespace>.example.io/<project_slug>`
The following is the minimum setup that you can use Pages with. It is the base for all
other setups as described below. NGINX proxies all requests to the daemon.
The Pages daemon doesn't listen to the outside world.
To configure GitLab Pages to use wildcard domains:
1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`:
@ -181,30 +204,38 @@ The Pages daemon doesn't listen to the outside world.
1. [Reconfigure GitLab](../restart_gitlab.md#reconfigure-a-linux-package-installation).
Watch the [video tutorial](https://youtu.be/dD8c7WNcc6s) for this configuration.
The resulting URL scheme is `http://<namespace>.example.io/<project_slug>`.
### Pages domain without wildcard DNS
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
For an overview, see [How to Enable GitLab Pages for GitLab CE and EE](https://youtu.be/dD8c7WNcc6s).
<!-- Video published on 2017-02-22 -->
### Single-domain sites
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/17584) as an [experiment](../../policy/experiment-beta-support.md) in GitLab 16.7.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148621) to [beta](../../policy/experiment-beta-support.md) in GitLab 16.11.
> - [Changed](https://gitlab.com/gitlab-org/gitlab-pages/-/issues/1111) implementation from NGINX to the GitLab Pages codebase in GitLab 17.2.
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/483365) in GitLab 17.4.
This configuration is the minimum setup for GitLab Pages. It is the base for all
other configurations. In this configuration, NGINX proxies all requests to the daemon,
because the GitLab Pages daemon doesn't listen to the outside world.
The following configuration is the minimum setup to use GitLab Pages.
It is the foundation for all other setups described here.
In this configuration:
- NGINX proxies all requests to the GitLab Pages daemon.
- The GitLab Pages daemon doesn't listen directly to the public internet.
Prerequisites:
- You have configured DNS setup
[without a wildcard](#for-namespace-in-url-path-without-wildcard-dns).
- You have configured DNS for
[single-domain sites](#dns-configuration-for-single-domain-sites).
To configure GitLab Pages to use single-domain sites:
1. In `/etc/gitlab/gitlab.rb`, set the external URL for GitLab Pages, and enable the feature:
```ruby
# External_url here is only for reference
external_url "http://example.com"
pages_external_url 'http://example.io'
external_url "http://example.com" # Swap out this URL for your own
pages_external_url 'http://example.io' # Important: not a subdomain of external_url, so cannot be http://pages.example.com
# Set this flag to enable this feature
gitlab_pages["namespace_in_path"] = true
@ -216,9 +247,9 @@ The resulting URL scheme is `http://example.io/<namespace>/<project_slug>`.
WARNING:
GitLab Pages supports only one URL scheme at a time:
with wildcard DNS, or without wildcard DNS.
wildcard domains or single-domain sites.
If you enable `namespace_in_path`, existing GitLab Pages websites
are accessible only on domains without wildcard DNS.
are accessible only on single-domain.
### Wildcard domains with TLS support
@ -227,12 +258,8 @@ Prerequisites:
- [Wildcard DNS setup](#dns-configuration)
- TLS certificate. Can be either Wildcard, or any other type meeting the [requirements](../../user/project/pages/custom_domains_ssl_tls_certification/index.md#manual-addition-of-ssltls-certificates).
---
URL scheme: `https://<namespace>.example.io/<project_slug>`
NGINX proxies all requests to the daemon. Pages daemon doesn't listen to the
outside world.
public internet.
1. Place the wildcard TLS certificate for `*.example.io` and the key inside `/etc/gitlab/ssl`.
1. In `/etc/gitlab/gitlab.rb` specify the following configuration:
@ -257,6 +284,8 @@ outside world.
[System OAuth application](../../integration/oauth_provider.md#create-an-instance-wide-application)
to use the HTTPS protocol.
The resulting URL scheme is `https://<namespace>.example.io/<project_slug>`.
WARNING:
Multiple wildcards for one instance is not supported. Only one wildcard per instance can be assigned.
@ -266,7 +295,7 @@ Before you reconfigure, remove the `gitlab_pages` section from `/etc/gitlab/gitl
then run `gitlab-ctl reconfigure`. For more information, read
[GitLab Pages does not regenerate OAuth](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/3947).
### Pages domain with TLS support, without wildcard DNS
### Single-domain sites with TLS support
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/17584) as an [experiment](../../policy/experiment-beta-support.md) in GitLab 16.7.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148621) to [beta](../../policy/experiment-beta-support.md) in GitLab 16.11.
@ -275,20 +304,19 @@ then run `gitlab-ctl reconfigure`. For more information, read
Prerequisites:
- You have configured DNS setup
[without a wildcard](#for-namespace-in-url-path-without-wildcard-dns).
- You have configured DNS for
[single-domain sites](#dns-configuration-for-single-domain-sites).
- You have a TLS certificate that covers your domain (like `example.io`).
In this configuration, NGINX proxies all requests to the daemon. The GitLab Pages
daemon doesn't listen to the outside world:
daemon doesn't listen to the public internet:
1. Add your TLS certificate and key as mentioned in the prerequisites into `/etc/gitlab/ssl`.
1. In `/etc/gitlab/gitlab.rb`, set the external URL for GitLab Pages, and enable the feature:
```ruby
# The external_url field is here only for reference.
external_url "https://example.com"
pages_external_url 'https://example.io'
external_url "https://example.com" # Swap out this URL for your own
pages_external_url 'https://example.io' # Important: not a subdomain of external_url, so cannot be https://pages.example.com
pages_nginx['redirect_http_to_https'] = true
@ -322,9 +350,9 @@ The resulting URL scheme is `https://example.io/<namespace>/<project_slug>`.
WARNING:
GitLab Pages supports only one URL scheme at a time:
with wildcard DNS, or without wildcard DNS.
wildcard domains or single-domain sites.
If you enable `namespace_in_path`, existing GitLab Pages websites
are accessible only on domains without wildcard DNS.
are accessible only as single-domain sites.
### Wildcard domains with TLS-terminating Load Balancer
@ -333,10 +361,6 @@ Prerequisites:
- [Wildcard DNS setup](#dns-configuration)
- [TLS-terminating load balancer](../../install/aws/index.md#load-balancer)
---
URL scheme: `https://<namespace>.example.io/<project_slug>`
This setup is primarily intended to be used when [installing a GitLab POC on Amazon Web Services](../../install/aws/index.md). This includes a TLS-terminating [classic load balancer](../../install/aws/index.md#load-balancer) that listens for HTTPS connections, manages TLS certificates, and forwards HTTP traffic to the instance.
1. In `/etc/gitlab/gitlab.rb` specify the following configuration:
@ -353,6 +377,8 @@ This setup is primarily intended to be used when [installing a GitLab POC on Ama
1. [Reconfigure GitLab](../restart_gitlab.md#reconfigure-a-linux-package-installation).
The resulting URL scheme is `https://<namespace>.example.io/<project_slug>`.
### Global settings
Below is a table of all configuration settings known to Pages in a Linux package installation,
@ -403,7 +429,7 @@ control over how the Pages daemon runs and serves content in your environment.
| `log_directory` | Absolute path to a log directory. |
| `log_format` | The log output format: `text` or `json`. |
| `log_verbose` | Verbose logging, true/false. |
| `namespace_in_path` | (Beta) Enable or disable namespace in the URL path to support [without wildcard DNS setup](#for-namespace-in-url-path-without-wildcard-dns). Default: `false`. |
| `namespace_in_path` | Enable or disable namespace in the URL path to support [single-domain sites DNS setup](#dns-configuration-for-single-domain-sites). Default: `false`. |
| `propagate_correlation_id` | Set to true (false by default) to re-use existing Correlation ID from the incoming request header `X-Request-ID` if present. If a reverse proxy sets this header, the value is propagated in the request chain. |
| `max_connections` | Limit on the number of concurrent connections to the HTTP, HTTPS or proxy listeners. |
| `max_uri_length` | The maximum length of URIs accepted by GitLab Pages. Set to 0 for unlimited length. |
@ -459,10 +485,6 @@ Prerequisites:
- [Wildcard DNS setup](#dns-configuration)
- Secondary IP
---
URL scheme: `http://<namespace>.example.io/<project_slug>` and `http://custom-domain.com`
In that case, the Pages daemon is running, NGINX still proxies requests to
the daemon but the daemon is also able to receive requests from the outside
world. Custom domains are supported, but no TLS.
@ -481,6 +503,8 @@ world. Custom domains are supported, but no TLS.
1. [Reconfigure GitLab](../restart_gitlab.md#reconfigure-a-linux-package-installation).
The resulting URL schemes are `http://<namespace>.example.io/<project_slug>` and `http://custom-domain.com`.
### Custom domains with TLS support
Prerequisites:
@ -489,10 +513,6 @@ Prerequisites:
- TLS certificate. Can be either Wildcard, or any other type meeting the [requirements](../../user/project/pages/custom_domains_ssl_tls_certification/index.md#manual-addition-of-ssltls-certificates).
- Secondary IP
---
URL scheme: `https://<namespace>.example.io/<project_slug>` and `https://custom-domain.com`
In that case, the Pages daemon is running, NGINX still proxies requests to
the daemon but the daemon is also able to receive requests from the outside
world. Custom domains and TLS are supported.
@ -526,6 +546,8 @@ world. Custom domains and TLS are supported.
[System OAuth application](../../integration/oauth_provider.md#create-an-instance-wide-application)
to use the HTTPS protocol.
The resulting URL schemes are `https://<namespace>.example.io/<project_slug>` and `https://custom-domain.com`.
### Custom domain verification
To prevent malicious users from hijacking domains that don't belong to them,

View File

@ -39,7 +39,7 @@ Before setting up a self-hosted model infrastructure, you must have:
- A [supported model](supported_models_and_hardware_requirements.md) (either cloud-based or on-premises).
- A [supported serving platform](supported_llm_serving_platforms.md) (either cloud-based or on-premises).
- A locally hosted or GitLab.com AI Gateway.
- GitLab [Enterprise Edition license](../../administration/license.md).
- GitLab Ultimate + [Duo Enterprise license](https://about.gitlab.com/solutions/gitlab-duo-pro/sales/?toggle=gitlab-duo-pro).
## Choose a configuration type

View File

@ -16,7 +16,7 @@ DETAILS:
> - [Enabled on self-managed](https://gitlab.com/groups/gitlab-org/-/epics/15176) in GitLab 17.6.
> - Changed to require GitLab Duo add-on in GitLab 17.6 and later.
To deploy self-hosted AI models, you need **GitLab Enterprise Edition**. For more information about licensing, refer to the [licensing documentation](../../administration/license.md).
To deploy self-hosted AI models, you need GitLab Ultimate and Duo Enterprise - [Start a trial](https://about.gitlab.com/solutions/gitlab-duo-pro/sales/?type=free-trial).
## Offerings

View File

@ -231,6 +231,9 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" \
Get a list of tags for given registry repository.
NOTE:
Offset pagination is deprecated and keyset pagination is now the preferred pagination method.
```plaintext
GET /projects/:id/registry/repositories/:repository_id/tags
```

View File

@ -12441,6 +12441,30 @@ The edge type for [`CiMinutesProjectMonthlyUsage`](#ciminutesprojectmonthlyusage
| <a id="ciminutesprojectmonthlyusageedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="ciminutesprojectmonthlyusageedgenode"></a>`node` | [`CiMinutesProjectMonthlyUsage`](#ciminutesprojectmonthlyusage) | The item at the end of the edge. |
#### `CiProjectSubscriptionConnection`
The connection type for [`CiProjectSubscription`](#ciprojectsubscription).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="ciprojectsubscriptionconnectioncount"></a>`count` | [`Int!`](#int) | Total count of collection. |
| <a id="ciprojectsubscriptionconnectionedges"></a>`edges` | [`[CiProjectSubscriptionEdge]`](#ciprojectsubscriptionedge) | A list of edges. |
| <a id="ciprojectsubscriptionconnectionnodes"></a>`nodes` | [`[CiProjectSubscription]`](#ciprojectsubscription) | A list of nodes. |
| <a id="ciprojectsubscriptionconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
#### `CiProjectSubscriptionEdge`
The edge type for [`CiProjectSubscription`](#ciprojectsubscription).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="ciprojectsubscriptionedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="ciprojectsubscriptionedgenode"></a>`node` | [`CiProjectSubscription`](#ciprojectsubscription) | The item at the end of the edge. |
#### `CiProjectVariableConnection`
The connection type for [`CiProjectVariable`](#ciprojectvariable).
@ -19990,6 +20014,17 @@ CI/CD variables given to a manual job.
| <a id="ciminutesprojectmonthlyusageproject"></a>`project` | [`Project`](#project) | Project having the recorded usage. |
| <a id="ciminutesprojectmonthlyusagesharedrunnersduration"></a>`sharedRunnersDuration` | [`Int`](#int) | Total duration (in seconds) of shared runners use by the project for the month. |
### `CiProjectSubscription`
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="ciprojectsubscriptionauthor"></a>`author` | [`UserCore`](#usercore) | Author of the subscription. |
| <a id="ciprojectsubscriptiondownstreamproject"></a>`downstreamProject` | [`CiSubscriptionsProjectDetails`](#cisubscriptionsprojectdetails) | Downstream project of the subscription.When an upstream project's pipeline completes, a pipeline is triggered in the downstream project. |
| <a id="ciprojectsubscriptionid"></a>`id` | [`CiSubscriptionsProjectID`](#cisubscriptionsprojectid) | Global ID of the subscription. |
| <a id="ciprojectsubscriptionupstreamproject"></a>`upstreamProject` | [`CiSubscriptionsProjectDetails`](#cisubscriptionsprojectdetails) | Upstream project of the subscription.When an upstream project's pipeline completes, a pipeline is triggered in the downstream project. |
### `CiProjectVariable`
CI/CD variables for a project.
@ -20290,6 +20325,26 @@ Represents the Geo replication and verification state of a ci_secure_file.
| <a id="cisubscriptionsprojectid"></a>`id` | [`CiSubscriptionsProjectID`](#cisubscriptionsprojectid) | Global ID of the subscription. |
| <a id="cisubscriptionsprojectupstreamproject"></a>`upstreamProject` | [`Project`](#project) | Upstream project of the subscription. |
### `CiSubscriptionsProjectDetails`
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="cisubscriptionsprojectdetailsid"></a>`id` | [`ID!`](#id) | ID of the project. |
| <a id="cisubscriptionsprojectdetailsname"></a>`name` | [`ID!`](#id) | Full path of the project. |
| <a id="cisubscriptionsprojectdetailsnamespace"></a>`namespace` | [`CiSubscriptionsProjectNamespaceDetails!`](#cisubscriptionsprojectnamespacedetails) | Namespace of the project. |
| <a id="cisubscriptionsprojectdetailsweburl"></a>`webUrl` | [`String`](#string) | Web URL of the project. |
### `CiSubscriptionsProjectNamespaceDetails`
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="cisubscriptionsprojectnamespacedetailsid"></a>`id` | [`ID!`](#id) | ID of the project. |
| <a id="cisubscriptionsprojectnamespacedetailsname"></a>`name` | [`ID!`](#id) | Full path of the project. |
### `CiTemplate`
GitLab CI/CD configuration template.
@ -30567,10 +30622,12 @@ Project-level settings for product analytics provider.
| <a id="projectciaccessauthorizedagents"></a>`ciAccessAuthorizedAgents` | [`ClusterAgentAuthorizationCiAccessConnection`](#clusteragentauthorizationciaccessconnection) | Authorized cluster agents for the project through ci_access keyword. (see [Connections](#connections)) |
| <a id="projectcicdsettings"></a>`ciCdSettings` | [`ProjectCiCdSetting`](#projectcicdsetting) | CI/CD settings for the project. |
| <a id="projectciconfigpathordefault"></a>`ciConfigPathOrDefault` | [`String!`](#string) | Path of the CI configuration file. |
| <a id="projectcidownstreamprojectsubscriptions"></a>`ciDownstreamProjectSubscriptions` **{warning-solid}** | [`CiProjectSubscriptionConnection`](#ciprojectsubscriptionconnection) | **Introduced** in GitLab 17.6. **Status**: Experiment. Pipeline subscriptions where this project is the upstream project.When this project's pipeline completes, a pipeline is triggered in the downstream project. |
| <a id="projectcijobtokenauthlogs"></a>`ciJobTokenAuthLogs` **{warning-solid}** | [`CiJobTokenAuthLogConnection`](#cijobtokenauthlogconnection) | **Introduced** in GitLab 17.6. **Status**: Experiment. The CI Job Tokens authorization logs. |
| <a id="projectcijobtokenscope"></a>`ciJobTokenScope` | [`CiJobTokenScopeType`](#cijobtokenscopetype) | The CI Job Tokens scope of access. |
| <a id="projectcisubscribedprojects"></a>`ciSubscribedProjects` | [`CiSubscriptionsProjectConnection`](#cisubscriptionsprojectconnection) | Pipeline subscriptions for projects subscribed to the project. (see [Connections](#connections)) |
| <a id="projectcisubscriptionsprojects"></a>`ciSubscriptionsProjects` | [`CiSubscriptionsProjectConnection`](#cisubscriptionsprojectconnection) | Pipeline subscriptions for the project. (see [Connections](#connections)) |
| <a id="projectcisubscribedprojects"></a>`ciSubscribedProjects` **{warning-solid}** | [`CiSubscriptionsProjectConnection`](#cisubscriptionsprojectconnection) | **Deprecated** in GitLab 17.6. Use `ciDownstreamProjectSubscriptions`. |
| <a id="projectcisubscriptionsprojects"></a>`ciSubscriptionsProjects` **{warning-solid}** | [`CiSubscriptionsProjectConnection`](#cisubscriptionsprojectconnection) | **Deprecated** in GitLab 17.6. Use `ciUpstreamProjectSubscriptions`. |
| <a id="projectciupstreamprojectsubscriptions"></a>`ciUpstreamProjectSubscriptions` **{warning-solid}** | [`CiProjectSubscriptionConnection`](#ciprojectsubscriptionconnection) | **Introduced** in GitLab 17.6. **Status**: Experiment. Pipeline subscriptions where this project is the downstream project.When an upstream project's pipeline completes, a pipeline is triggered in the downstream project (this project). |
| <a id="projectcodecoveragesummary"></a>`codeCoverageSummary` | [`CodeCoverageSummary`](#codecoveragesummary) | Code coverage summary associated with the project. |
| <a id="projectcomplianceframeworks"></a>`complianceFrameworks` | [`ComplianceFrameworkConnection`](#complianceframeworkconnection) | Compliance frameworks associated with the project. (see [Connections](#connections)) |
| <a id="projectcontainerexpirationpolicy"></a>`containerExpirationPolicy` **{warning-solid}** | [`ContainerExpirationPolicy`](#containerexpirationpolicy) | **Deprecated** in GitLab 17.5. Use `container_tags_expiration_policy`. |

View File

@ -7,12 +7,12 @@ info: Analysis of Application Settings for Cells 1.0.
## Statistics
- Number of attributes: 499
- Number of attributes: 503
- Number of encrypted attributes: 43 (9.0%)
- Number of attributes documented: 309 (62.0%)
- Number of attributes on GitLab.com different from the defaults: 218 (44.0%)
- Number of attributes with `clusterwide` set: 498 (100.0%)
- Number of attributes with `clusterwide: true` set: 120 (24.0%)
- Number of attributes documented: 310 (62.0%)
- Number of attributes on GitLab.com different from the defaults: 220 (44.0%)
- Number of attributes with `clusterwide` set: 503 (100.0%)
- Number of attributes with `clusterwide: true` set: 121 (24.0%)
## Individual columns
@ -33,7 +33,7 @@ info: Analysis of Application Settings for Cells 1.0.
| `allow_possible_spam` | `false` | `boolean` | `` | `true` | `false` | `false` | `false`| `false` |
| `allow_project_creation_for_guest_and_below` | `false` | `boolean` | `boolean` | `true` | `true` | `false` | `false`| `true` |
| `allow_runner_registration_token` | `false` | `boolean` | `boolean` | `true` | `true` | `false` | `false`| `true` |
| `allow_top_level_group_owners_to_create_service_accounts` | `false` | `boolean` | `` | `true` | `false` | `false` | `???`| `false` |
| `allow_top_level_group_owners_to_create_service_accounts` | `false` | `boolean` | `` | `true` | `false` | `false` | `false`| `false` |
| `anthropic_api_key` | `true` | `bytea` | `` | `false` | `null` | `false` | `false`| `false` |
| `archive_builds_in_seconds` | `false` | `integer` | `` | `false` | `null` | `false` | `false`| `false` |
| `arkose_labs_client_secret` | `true` | `bytea` | `` | `false` | `null` | `true` | `true`| `false` |
@ -240,6 +240,7 @@ info: Analysis of Application Settings for Cells 1.0.
| `housekeeping_incremental_repack_period` | `false` | `integer` | `integer` | `true` | `10` | `false` | `false`| `true` |
| `html_emails_enabled` | `false` | `boolean` | `boolean` | `false` | `true` | `false` | `false`| `true` |
| `id` | `false` | `bigint` | `` | `true` | `???` | `false` | `false`| `false` |
| `identity_verification_settings` | `false` | `jsonb` | `` | `true` | `'{}'::jsonb` | `true` | `true`| `false` |
| `import_sources` | `false` | `text` | `array of strings` | `false` | `null` | `true` | `true`| `true` |
| `importers` | `false` | `jsonb` | `` | `true` | `'{}'::jsonb` | `true` | `true`| `false` |
| `inactive_projects_delete_after_months` | `false` | `integer` | `` | `true` | `2` | `false` | `false`| `false` |
@ -420,6 +421,7 @@ info: Analysis of Application Settings for Cells 1.0.
| `sidekiq_job_limiter_compression_threshold_bytes` | `false` | `integer` | `integer` | `true` | `100000` | `false` | `false`| `true` |
| `sidekiq_job_limiter_limit_bytes` | `false` | `integer` | `integer` | `true` | `0` | `true` | `false`| `true` |
| `sidekiq_job_limiter_mode` | `false` | `smallint` | `string` | `true` | `1` | `false` | `false`| `true` |
| `sign_in_restrictions` | `false` | `jsonb` | `` | `true` | `'{}'::jsonb` | `true` | `false`| `false` |
| `signup_enabled` | `false` | `boolean` | `boolean` | `false` | `null` | `true` | `false`| `true` |
| `silent_mode_enabled` | `false` | `boolean` | `boolean` | `true` | `false` | `false` | `false`| `true` |
| `slack_app_enabled` | `false` | `boolean` | `boolean` | `false` | `false` | `true` | `false`| `true` |
@ -486,6 +488,7 @@ info: Analysis of Application Settings for Cells 1.0.
| `throttle_unauthenticated_period_in_seconds` | `false` | `integer` | `integer` | `true` | `3600` | `true` | `false`| `true` |
| `throttle_unauthenticated_requests_per_period` | `false` | `integer` | `integer` | `true` | `3600` | `true` | `false`| `true` |
| `time_tracking_limit_to_hours` | `false` | `boolean` | `boolean` | `true` | `false` | `true` | `false`| `true` |
| `transactional_emails` | `false` | `jsonb` | `` | `true` | `'{}'::jsonb` | `false` | `false`| `false` |
| `two_factor_grace_period` | `false` | `integer` | `integer` | `false` | `48` | `false` | `false`| `true` |
| `unconfirmed_users_delete_after_days` | `false` | `integer` | `integer` | `true` | `7` | `true` | `true`| `true` |
| `unique_ips_limit_enabled` | `false` | `boolean` | `boolean` | `true` | `false` | `false` | `false`| `true` |

View File

@ -2073,7 +2073,7 @@ Searching is different from [filtering](#filter).
When referring to the subscription billing model:
- For GitLab SaaS, use **seats**. Customers purchase seats. Users occupy seats when they are invited
- For GitLab.com, use **seats**. Customers purchase seats. Users occupy seats when they are invited
to a group, with some [exceptions](../../../subscriptions/gitlab_com/index.md#how-seat-usage-is-determined).
- For GitLab self-managed, use **users**. Customers purchase subscriptions for a specified number of **users**.

View File

@ -0,0 +1,327 @@
---
stage: Create
group: Source Code
description: Common commands and workflows.
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"
---
# File management
Git provides file management capabilities that help you to track changes,
collaborate with others, and manage large files efficiently.
## File history
Use `git log` to view a file's complete history and understand how it has changed over time.
The file history shows you:
- The author of each change.
- The date and time of each modification.
- The specific changes made in each commit.
For example, to view `history` information about the `CONTRIBUTING.md` file in the root
of the `gitlab` repository, run:
```shell
git log CONTRIBUTING.md
```
Example output:
```shell
commit b350bf041666964c27834885e4590d90ad0bfe90
Author: Nick Malcolm <nmalcolm@gitlab.com>
Date: Fri Dec 8 13:43:07 2023 +1300
Update security contact and vulnerability disclosure info
commit 8e4c7f26317ff4689610bf9d031b4931aef54086
Author: Brett Walker <bwalker@gitlab.com>
Date: Fri Oct 20 17:53:25 2023 +0000
Fix link to Code of Conduct
and condense some of the verbiage
```
## Check previous changes to a file
Use `git blame` to see who made the last change to a file and when.
This helps to understand the context of a file's content,
resolve conflicts, and identify the person responsible for a specific change.
If you want to find `blame` information about a `README.md` file in the local directory:
1. Open a terminal or command prompt.
1. Go to your Git repository.
1. Run the following command:
```shell
git blame README.md
```
1. To navigate the results page, press <kbd>Space</kbd>.
1. To exit out of the results, press <kbd>Q</kbd>.
This output displays the file content with annotations showing the commit SHA, author,
and date for each line. For example:
```shell
58233c4f1054c (Dan Rhodes 2022-05-13 07:02:20 +0000 1) ## Contributor License Agreement
b87768f435185 (Jamie Hurewitz 2017-10-31 18:09:23 +0000 2)
8e4c7f26317ff (Brett Walker 2023-10-20 17:53:25 +0000 3) Contributions to this repository are subject to the
58233c4f1054c (Dan Rhodes 2022-05-13 07:02:20 +0000 4)
```
## Git LFS
Git Large File Storage (LFS) is an extension that helps you manage large files in Git repositories.
It replaces large files with text pointers in Git, and stores the file contents on a remote server.
Prerequisites:
- Download and install the appropriate version of the [CLI extension for Git LFS](https://git-lfs.com) for your operating system.
- [Configure your project to use Git LFS](lfs/index.md).
- Install the Git LFS pre-push hook. To do this, run `git lfs install` in the root directory of your repository.
### Add and track files
To add a large file into your Git repository and track it with Git LFS:
1. Configure tracking for all files of a certain type. Replace `iso` with your desired file type:
```shell
git lfs track "*.iso"
```
This command creates a `.gitattributes` file with instructions to handle all
ISO files with Git LFS. The following line is added to your `.gitattributes` file:
```plaintext
*.iso filter=lfs -text
```
1. Add a file of that type, `.iso`, to your repository.
1. Track the changes to both the `.gitattributes` file and the `.iso` file:
```shell
git add .
```
1. Ensure you've added both files:
```shell
git status
```
The `.gitattributes` file must be included in your commit.
It if isn't included, Git does not track the ISO file with Git LFS.
NOTE:
Ensure the files you're changing are not listed in a `.gitignore` file.
If they are, Git commits the change locally but doesn't push it to your upstream repository.
1. Commit both files to your local copy of the repository:
```shell
git commit -m "Add an ISO file and .gitattributes"
```
1. Push your changes upstream. Replace `main` with the name of your branch:
```shell
git push origin main
```
1. Create a merge request.
NOTE:
When you add a new file type to Git LFS tracking, existing files of this type
are not converted to Git LFS. Only files of this type, added after you begin tracking, are added to Git LFS. Use `git lfs migrate` to convert existing files to use Git LFS.
### Stop tracking a file
When you stop tracking a file with Git LFS, the file remains on disk because it's still
part of your repository's history.
To stop tracking a file with Git LFS:
1. Run the `git lfs untrack` command and provide the path to the file:
```shell
git lfs untrack doc/example.iso
```
1. Use the `touch` command to convert it back to a standard file:
```shell
touch doc/example.iso
```
1. Track the changes to the file:
```shell
git add .
```
1. Commit and push your changes.
1. Create a merge request and request a review.
1. Merge the request into the target branch.
NOTE:
If you delete an object tracked by Git LFS, without tracking it with `git lfs untrack`,
the object shows as `modified` in `git status`.
### Stop tracking all files of a single type
To stop tracking all files of a particular type in Git LFS:
1. Run the `git lfs untrack` command and provide the file type to stop tracking:
```shell
git lfs untrack "*.iso"
```
1. Use the `touch` command to convert the files back to standard files:
```shell
touch *.iso
```
1. Track the changes to the files:
```shell
git add .
```
1. Commit and push your changes.
1. Create a merge request and request a review.
1. Merge the request into the target branch.
## File locks
File locks help prevent conflicts and ensure that only one person can edit a file at a time.
It's a good option for:
- Binary files that can't be merged. For example, design files and videos.
- Files that require exclusive access during editing.
Prerequisites:
- You must have [Git LFS installed](../../topics/git/lfs/index.md).
- You must have the Maintainer role for the project.
### Configure file locks
To configure file locks for a specific file type:
1. Use the `git lfs track` command with the `--lockable` option. For example, to configure PNG files:
```shell
git lfs track "*.png" --lockable
```
This command creates or updates your `.gitattributes` file with the following content:
```plaintext
*.png filter=lfs diff=lfs merge=lfs -text lockable
```
1. Push the `.gitattributes` file to the remote repository for the changes to take effect.
NOTE:
After a file type is registered as lockable, it is automatically marked as read-only.
#### Configure file locks without LFS
To register a file type as lockable without using Git LFS:
1. Edit the `.gitattributes` file manually:
```shell
*.pdf lockable
```
1. Push the `.gitattributes` file to the remote repository.
### Lock and unlock files
To lock or unlock a file with exclusive file locking:
1. Open a terminal window in your repository directory.
1. Run one of the following commands:
::Tabs
:::TabTitle Lock a file
```shell
git lfs lock path/to/file.png
```
:::TabTitle Unlock a file
```shell
git lfs unlock path/to/file.png
```
:::TabTitle Unlock a file by ID
```shell
git lfs unlock --id=123
```
:::TabTitle Force unlock a file
```shell
git lfs unlock --id=123 --force
```
::EndTabs
### View locked files
To view locked files:
1. Open a terminal window in your repository.
1. Run the following command:
```shell
git lfs locks
```
The output lists the locked files, the users who locked them, and the file IDs.
In the GitLab UI:
- The repository file tree displays an LFS badge for files tracked by Git LFS.
- Exclusively-locked files show a padlock icon.
LFS-Locked files
You can also [view and remove existing locks](../../user/project/file_lock.md) from the GitLab UI.
NOTE:
When you rename an exclusively-locked file, the lock is lost. You must lock it again to keep it locked.
### Lock and edit a file
To lock a file, edit it, and optionally unlock it:
1. Lock the file:
```shell
git lfs lock <file_path>
```
1. Edit the file.
1. Optional. Unlock the file when you're done:
```shell
git lfs unlock <file_path>
```
## Related topics
- [File management with the GitLab UI](../../user/project/repository/files/index.md)
- [Git Large File Storage (LFS) documentation](lfs/index.md)
- [File locking](../../user/project/file_lock.md)

View File

@ -81,171 +81,6 @@ It offers both server settings and project-specific settings.
handling for individual files and file types.
1. Add the files and file types you want to track with Git LFS.
## Add a file with Git LFS
Prerequisites:
- You have downloaded and installed the appropriate version of the
[CLI extension for Git LFS](https://git-lfs.com) for your operating system.
- Your project is [configured to use Git LFS](#configure-git-lfs-for-a-project).
To add a large file into your Git repository and immediately track it with Git LFS:
1. To track all files of a certain type with Git LFS, rather than a single file,
run this command, replacing `iso` with your desired file type:
```shell
git lfs track "*.iso"
```
This command creates a `.gitattributes` file with instructions to handle all
ISO files with Git LFS. The line in your `.gitattributes` file looks like this:
```plaintext
*.iso filter=lfs -text
```
1. Add a file of that type (`.iso`) to your repository.
1. Tell Git to track the changes to both the `.gitattributes` file and the `.iso` file:
```shell
git add .
```
1. To ensure you've added both files, run `git status`. If the `.gitattributes` file
isn't included in your commit, users who clone your repository don't get the
files they need.
1. Commit both files to your local copy of your repository:
```shell
git commit -am "Add an ISO file and .gitattributes"
```
1. Push your changes back upstream, replacing `main` with the name of your branch:
```shell
git push origin main
```
Make sure the files you are changing aren't listed in a `.gitignore` file.
If this file (or file type) is in your `.gitignore` file, Git commits
the change locally, but does not push it to your upstream repository.
1. Create your merge request.
### Add a file type to Git LFS
When you add a new file type into Git LFS tracking, existing files of this type
are _not_ converted to Git LFS. Files of this type added _after_ you begin
tracking are added to Git LFS. To convert existing files of that type to
use Git LFS, use `git lfs migrate`.
Prerequisites:
- You have downloaded and installed the appropriate version of the
[CLI extension for Git LFS](https://git-lfs.com) for your operating system.
- Your project is [configured to use Git LFS](#configure-git-lfs-for-a-project).
To start tracking a file type in Git LFS:
1. Make sure this file type isn't listed in your project's `.gitignore` file.
If this file type is in your `.gitignore` file, Git commits your changes
locally, but does not push it to your upstream repository.
1. Decide what file types to track with Git LFS. For each file type, run this
command, replacing `iso` with your desired file type:
```shell
git lfs track "*.iso"
```
1. Tell Git to track the changes to the `.gitattributes` file. Commit the
file to your local copy of your repository, replacing `iso` with your desired file type:
```shell
git add .
git commit -am "Use Git LFS for files of type .iso"
```
1. Push your changes back upstream, replacing `filetype` with the name of your branch:
```shell
git push origin filetype
```
## Stop tracking a file with Git LFS
When you stop tracking a file with Git LFS, the file remains on disk because it remains part of your repository's
history. To understand why, see [Delete a Git LFS file from repository history](#delete-a-git-lfs-file-from-repository-history).
Prerequisites:
- You have downloaded and installed the appropriate version of the
[CLI extension for Git LFS](https://git-lfs.com) for your operating system.
- You have installed the Git LFS pre-push hook by running `git lfs install`
in the root directory of your repository.
To stop tracking a file with Git LFS:
1. Run the [`git lfs untrack`](https://github.com/git-lfs/git-lfs/blob/main/docs/man/git-lfs-untrack.adoc)
command and provide the path to the file:
```shell
git lfs untrack doc/example.iso
```
1. Use the `touch` command to convert it back to a standard file:
```shell
touch doc/example.iso
```
1. Tell Git to track the changes to the file:
```shell
git add .
```
1. Commit and push your changes.
1. Create a merge request and request a review.
1. After you get the required approvals, merge the request into the target branch.
If you delete an object (`example.iso`) tracked by Git LFS, but don't use
the `git lfs untrack` command, `example.iso` shows as `modified` in `git status`.
### Stop tracking all files of a single type
Prerequisites:
- You have downloaded and installed the appropriate version of the
[CLI extension for Git LFS](https://git-lfs.com) for your operating system.
- You have installed the Git LFS pre-push hook by running `git lfs install`
in the root directory of your repository.
To stop tracking all files of a particular type in Git LFS:
1. Run the [`git lfs untrack`](https://github.com/git-lfs/git-lfs/blob/main/docs/man/git-lfs-untrack.adoc)
command and provide the file type to stop tracking:
```shell
git lfs untrack "*.iso"
```
1. Use the `touch` command to convert the files back to standard files:
```shell
touch *.iso
```
1. Tell Git to track the changes to the files:
```shell
git add .
```
1. Commit and push your changes.
1. Create a merge request and request a review.
1. After you get the required approvals, merge the request into the target branch.
## Enable or disable Git LFS for a project
Git LFS is enabled by default for both self-managed instances and GitLab.com.
@ -262,6 +97,12 @@ To enable or disable Git LFS at the project level:
1. Select the **Git Large File Storage (LFS)** toggle.
1. Select **Save changes**.
## Add and track files
You can add large files to Git LFS. This helps you manage files in Git repositories.
When you track files with Git LFS, they are replaced with text pointers in Git,
and stored on a remote server. For more information, see [Git LFS](../../../topics/git/file_management.md#git-lfs).
## Clone a repository that uses Git LFS
When you clone a repository that uses Git LFS, Git detects the LFS-tracked files
@ -308,8 +149,9 @@ the total size of your repository, see
## Related topics
- Use Git LFS to set up [exclusive file locks](../../../user/project/file_lock.md#exclusive-file-locks).
- Use Git LFS to set up [exclusive file locks](../file_management.md#configure-file-locks).
- Blog post: [Getting started with Git LFS](https://about.gitlab.com/blog/2017/01/30/getting-started-with-git-lfs-tutorial/)
- [Git LFS with Git](../../../topics/git/file_management.md#git-lfs)
- [Git LFS developer information](../../../development/lfs.md)
- [GitLab Git Large File Storage (LFS) Administration](../../../administration/lfs/index.md) for self-managed instances
- [Troubleshooting Git LFS](troubleshooting.md)

View File

@ -500,26 +500,6 @@ You can configure a custom limit on self-managed instances with the `scan_execut
<div class="deprecation breaking-change" data-milestone="18.0">
### List container registry repository tags API endpoint pagination
<div class="deprecation-notes">
- Announced in GitLab <span class="milestone">16.10</span>
- Removal in GitLab <span class="milestone">18.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change))
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/432470).
</div>
You can use the container registry REST API to [get a list of registry repository tags](https://docs.gitlab.com/ee/api/container_registry.html#list-registry-repository-tags). We plan to improve this endpoint, adding more metadata and new features like improved sorting and filtering.
While offset-based pagination was already available for this endpoint, keyset-based pagination was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/432470) in GitLab 16.10 for GitLab.com only. This is now the preferred pagination method.
Offset-based pagination for the [List registry repository tags](https://docs.gitlab.com/ee/api/container_registry.html#list-registry-repository-tags) endpoint is deprecated in GitLab 16.10 and will be removed in 18.0. Instead, use the keyset-based pagination.
</div>
<div class="deprecation breaking-change" data-milestone="18.0">
### OpenTofu CI/CD template
<div class="deprecation-notes">

View File

@ -27,7 +27,7 @@ said to have "released the lock".
GitLab supports two different modes of file locking:
- [Exclusive file locks](#exclusive-file-locks) for binary files: done
- [Exclusive file locks](../../topics/git/file_management.md#file-locks) for binary files: done
**through the command line** with Git LFS and `.gitattributes`, it prevents locked
files from being modified on any branch.
- [Default branch locks](#default-branch-file-and-directory-locks): done
@ -44,156 +44,6 @@ users are prevented from modifying locked files by pushing, merging,
or any other means, and are shown an error like:
`'.gitignore' is locked by @Administrator`.
## Exclusive file locks
This process allows you to lock single files or file extensions and it is
done through the command line. It doesn't require GitLab paid subscriptions.
Git LFS is well known for tracking files to reduce the storage of
Git repositories, but it can also be used for [locking files](https://github.com/git-lfs/git-lfs/wiki/File-Locking).
This is the method used for Exclusive File Locks.
### Install Git LFS
Before getting started, make sure you have [Git LFS installed](../../topics/git/lfs/index.md) in your computer. Open a terminal window and run:
```shell
git-lfs --version
```
If it doesn't recognize this command, you must install it. There are
several [installation methods](https://git-lfs.com/) that you can
choose according to your OS. To install it with Homebrew:
```shell
brew install git-lfs
```
Once installed, **open your local repository in a terminal window** and
install Git LFS in your repository. If you're sure that LFS is already installed,
you can skip this step. If you're unsure, re-installing it does no harm:
```shell
git lfs install
```
For more information, see [Git Large File Storage (LFS)](../../topics/git/lfs/index.md).
### Configure Exclusive File Locks
You need the Maintainer role
Exclusive File Locks for your project through the command line.
The first thing to do before using File Locking is to tell Git LFS which
kind of files are lockable. The following command stores PNG files
in LFS and flag them as lockable:
```shell
git lfs track "*.png" --lockable
```
After executing the above command a file named `.gitattributes` is
created or updated with the following content:
```shell
*.png filter=lfs diff=lfs merge=lfs -text lockable
```
You can also register a file type as lockable without using LFS (to be able, for example,
to lock/unlock a file you need in a remote server that
implements the LFS File Locking API). To do that you can edit the
`.gitattributes` file manually:
```shell
*.pdf lockable
```
The `.gitattributes` file is key to the process and **must**
be pushed to the remote repository for the changes to take effect.
After a file type has been registered as lockable, Git LFS makes
them read-only on the file system automatically. This means you
must **lock the file** before [editing it](#edit-lockable-files).
### Lock files
By locking a file, you verify that no one else is editing it, and
prevent anyone else from editing the file until you're done. On the other
hand, when you unlock a file, you communicate that you've finished editing
and allow other people to edit it.
To lock or unlock a file with Exclusive File Locking, open a terminal window
in your repository directory and run the commands as described below.
To **lock** a file:
```shell
git lfs lock path/to/file.png
```
To **unlock** a file:
```shell
git lfs unlock path/to/file.png
```
You can also unlock by file ID (given by LFS when you [view locked files](#view-exclusively-locked-files)):
```shell
git lfs unlock --id=123
```
If for some reason you need to unlock a file that was not locked by
yourself, you can use the `--force` flag as long as you have **Maintainer**
permissions to the project:
```shell
git lfs unlock --id=123 --force
```
You can push files to GitLab whether they're locked or unlocked.
NOTE:
Although multi-branch file locks can be created and managed through the Git LFS
command-line interface, file locks can be created for any file.
### View exclusively-locked files
To list all the files locked with LFS locally, open a terminal window in your
repository and run:
```shell
git lfs locks
```
The output lists the locked files followed by the user who locked each of them
and the files' IDs.
On the repository file tree, GitLab displays an LFS badge for files
tracked by Git LFS plus a padlock icon on exclusively-locked files:
![LFS-Locked files](img/lfs_locked_files_v17_4.png)
You can also [view and remove existing locks](#view-and-remove-existing-locks) from the GitLab UI.
NOTE:
When you rename an exclusively-locked file, the lock is lost. You must
lock it again to keep it locked.
### Edit lockable files
After the file is [configured as lockable](#configure-exclusive-file-locks), it is set to read-only.
Therefore, you need to lock it before editing it.
Suggested workflow for shared projects:
1. Lock the file.
1. Edit the file.
1. Commit your changes.
1. Push to the repository.
1. Get your changes reviewed, approved, and merged.
1. Unlock the file.
## Default branch file and directory locks
DETAILS:
@ -234,3 +84,8 @@ This list shows all the files locked either through LFS or GitLab UI.
Locks can be removed by their author, or any user
with at least the Maintainer role.
## Related topics
- [File management with Git](../../topics/git/file_management.md)
- [File locks](../../topics/git/file_management.md#file-locks)

View File

@ -55,35 +55,8 @@ To see earlier revisions of a specific line:
1. Select **View blame prior to this change** (**{doc-versions}**)
until you've found the changes you're interested in viewing.
## Associated `git` command
If you're running `git` from the command line, the equivalent command is
`git blame <filename>`. For example, if you want to find `blame` information
about a `README.md` file in the local directory:
1. Run this command `git blame README.md`.
1. If the line you want to see is not in the first page of results, press <kbd>Space</kbd>
until you find the line you want.
1. To exit out of the results, press <kbd>Q</kbd>.
The `git blame` output in the CLI looks like this:
```shell
58233c4f1054c (Dan Rhodes 2022-05-13 07:02:20 +0000 1) ## Contributor License Agreement
b87768f435185 (Jamie Hurewitz 2017-10-31 18:09:23 +0000 2)
8e4c7f26317ff (Brett Walker 2023-10-20 17:53:25 +0000 3) Contributions to this repository are subject to the
58233c4f1054c (Dan Rhodes 2022-05-13 07:02:20 +0000 4)
```
The output includes:
- The SHA of the commit.
- The name of the committer.
- The date and time in UTC format.
- The line number.
- The contents of the line.
## Related topics
- [Git file blame REST API](../../../../api/repository_files.md#get-file-blame-from-repository)
- [Common Git commands](../../../../topics/git/commands.md)
- [File management with Git](../../../../topics/git/file_management.md)

View File

@ -31,7 +31,7 @@ GitLab retrieves the user name and email information from the
[Git configuration](https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration)
of the contributor when the user creates a commit.
## View a file's Git history in the UI
## View a file's Git history
To see a file's Git history in the UI:
@ -40,34 +40,11 @@ To see a file's Git history in the UI:
1. Go to your desired file in the repository.
1. In the upper-right corner, select **History**.
## In the CLI
To see the history of a file from the command line, use the `git log <filename>` command.
For example, to see `history` information about the `CONTRIBUTING.md` file in the root
of the `gitlab` repository, run this command:
```shell
$ git log CONTRIBUTING.md
commit b350bf041666964c27834885e4590d90ad0bfe90
Author: Nick Malcolm <nmalcolm@gitlab.com>
Date: Fri Dec 8 13:43:07 2023 +1300
Update security contact and vulnerability disclosure info
commit 8e4c7f26317ff4689610bf9d031b4931aef54086
Author: Brett Walker <bwalker@gitlab.com>
Date: Fri Oct 20 17:53:25 2023 +0000
Fix link to Code of Conduct
and condense some of the verbiage
```
## Related topics
- [Git blame](git_blame.md) for line-by-line information about a file
- [Common Git commands](../../../../topics/git/commands.md)
- [File management with Git](../../../../topics/git/file_management.md)
## Troubleshooting

View File

@ -130,6 +130,7 @@ To change the default handling of a file or file type, create a
## Related topics
- [Repository files API](../../../../api/repository_files.md)
- [File management with Git](../../../../topics/git/file_management.md)
## Troubleshooting

View File

@ -30,8 +30,9 @@ const extendConfigs = [
// rewrite.
let jhConfigs = [];
if (existsSync(path.resolve(dirname, 'jh'))) {
// eslint-disable-next-line import/no-unresolved, import/extensions
jhConfigs = (await import('jh/eslint.config.js')).default;
const pathToJhConfig = path.resolve(dirname, 'jh/eslint.config.js')
// eslint-disable-next-line import/no-dynamic-require, no-unsanitized/method
jhConfigs = (await import(pathToJhConfig)).default;
}
const jestConfig = {

View File

@ -11,6 +11,17 @@ module Keeps
@rewriter = Parser::Source::TreeRewriter.new(@source.buffer)
end
class << self
# Define a node matcher method in the +RuboCop::AST::Node+, which all other node types inherits from.
def def_node_matcher(method_name, pattern)
RuboCop::AST::NodePattern.new(pattern).def_node_matcher(RuboCop::AST::Node, method_name)
define_method method_name do
source.ast.public_send(method_name) # rubocop:disable GitlabSecurity/PublicSend -- it's used to evaluate the node matcher at instance level
end
end
end
def replace_method_content(method_name, content, strip_comments_from_file: false)
method = source.ast.each_node(:class).first.each_node(:def).find do |child|
child.method_name == method_name.to_sym
@ -25,6 +36,12 @@ module Keeps
process
end
def replace_as_string(node, content)
rewriter.replace(node.loc.expression, content)
process
end
private
attr_reader :file, :source, :rewriter, :corrector
@ -52,7 +69,7 @@ module Keeps
end
def process
@process ||= rewriter.process.lstrip.gsub(/\n{3,}/, "\n\n")
rewriter.process.lstrip.gsub(/\n{3,}/, "\n\n")
end
end
end

View File

@ -0,0 +1,135 @@
# frozen_string_literal: true
require_relative '../rubocop/cop_todo'
module Keeps
# This is an implementation of ::Gitlab::Housekeeper::Keep.
# This changes workers which have `data_consistency: :always` to `:sticky`.
#
# You can run it individually with:
#
# ```
# bundle exec gitlab-housekeeper -d -k Keeps::UpdateWorkersDataConsistency
# ```
class UpdateWorkersDataConsistency < ::Gitlab::Housekeeper::Keep
WORKER_REGEX = %r{app/workers/(.+).rb}
WORKERS_DATA_CONSISTENCY_PATH = '.rubocop_todo/sidekiq_load_balancing/worker_data_consistency.yml'
FALLBACK_FEATURE_CATEGORY = 'database'
LIMIT_TO = 5
def initialize(...)
::Keeps::Helpers::FileHelper.def_node_matcher :data_consistency_node, <<~PATTERN
`(send nil? :data_consistency $(sym _) ...)
PATTERN
super
end
def each_change
workers_by_feature_category.deep_dup.each do |feature_category, workers|
remove_workers_from_list(workers.pluck(:path)) # rubocop:disable CodeReuse/ActiveRecord -- small dataset
workers.each do |worker|
file_helper = ::Keeps::Helpers::FileHelper.new(worker[:path])
node = file_helper.data_consistency_node
File.write(worker[:path], file_helper.replace_as_string(node, ':sticky'))
end
yield(build_change(feature_category, workers))
end
end
private
def workers_by_feature_category
worker_paths.each_with_object(Hash.new { |h, k| h[k] = [] }) do |worker_path, group|
next unless File.read(worker_path, mode: 'rb').include?('data_consistency :always')
worker_name = worker_path.match(WORKER_REGEX)[1].camelize
feature_category = worker_feature_category(worker_name)
next if group[feature_category].size >= LIMIT_TO
group[feature_category] << { path: worker_path, name: worker_name }
end
end
def build_change(feature_category, workers)
change = ::Gitlab::Housekeeper::Change.new
change.title = "Change data consistency for workers maintained by #{feature_category}".truncate(70, omission: '')
change.identifiers = workers.map { |worker| worker[:name].to_s }.prepend(feature_category)
change.labels = labels(feature_category)
change.reviewers = pick_reviewers(feature_category, change.identifiers)
change.changed_files = workers.pluck(:path).prepend(WORKERS_DATA_CONSISTENCY_PATH) # rubocop:disable CodeReuse/ActiveRecord -- small dataset
change.description = <<~MARKDOWN.chomp
## What does this MR
It updates workers data consistency from `:always` to `:sticky` for workers maintained by `#{feature_category}`,
as a way to reduce database reads on the primary DB. Check https://gitlab.com/gitlab-org/gitlab/-/issues/462611.
To reduce resource saturation on the primary node, all workers should be changed to `sticky`, at minimum.
Since jobs are now enqueued along with the current database LSN, the replica (for `:sticky` or `:delayed`)
is guaranteed to be caught up to that point, or the job will be retried, or use the primary. Consider updating
the worker(s) to `delayed`, if it's applicable.
You can read more about the Sidekiq Workers `data_consistency` in
https://docs.gitlab.com/ee/development/sidekiq/worker_attributes.html#job-data-consistency-strategies.
You can use this [dashboard](https://log.gprd.gitlab.net/app/r/s/iyIUV) to monitor the worker query activity on
primary vs. replicas.
Currently, the `gitlab-housekeeper` is not always capable of updating all references, so you must check the diff
and pipeline failures to confirm if there are any issues.
MARKDOWN
change
end
def labels(feature_category)
group_labels = groups_helper.labels_for_feature_category(feature_category)
group_labels + %w[maintenance::scalability type::maintenance severity::3 priority::1]
end
def pick_reviewers(feature_category, identifiers)
groups_helper.pick_reviewer_for_feature_category(
feature_category,
identifiers,
fallback_feature_category: 'database'
)
end
def worker_feature_category(worker_name)
feature_category = workers_meta.find { |entry| entry[:worker_name].to_s == worker_name.to_s } || {}
feature_category.fetch(:feature_category, FALLBACK_FEATURE_CATEGORY).to_s
end
def workers_meta
@workers_meta ||= Gitlab::SidekiqConfig::QUEUE_CONFIG_PATHS.flat_map do |yaml_file|
YAML.safe_load_file(yaml_file, permitted_classes: [Symbol])
end
end
def remove_workers_from_list(paths_to_remove)
todo_helper = RuboCop::CopTodo.new('SidekiqLoadBalancing/WorkerDataConsistency')
todo_helper.add_files(worker_paths - paths_to_remove)
File.write(WORKERS_DATA_CONSISTENCY_PATH, todo_helper.to_yaml)
end
def worker_paths
@worker_paths ||= YAML.safe_load_file(WORKERS_DATA_CONSISTENCY_PATH).dig(
'SidekiqLoadBalancing/WorkerDataConsistency',
'Exclude'
)
end
def groups_helper
@groups_helper ||= ::Keeps::Helpers::Groups.new
end
end
end

View File

@ -6,7 +6,7 @@ module API
before { authenticate! }
feature_category :team_planning
feature_category :notifications
urgency :low
ISSUABLE_TYPES = {

View File

@ -23,6 +23,12 @@ module Gitlab
end
end
def variables_hash_expanded
strong_memoize(:variables_hash_expanded) do
variables.sort_and_expand_all.to_hash
end
end
def project
pipeline.project
end

View File

@ -17,7 +17,7 @@ module Gitlab
return true unless modified_paths
return false if modified_paths.empty?
expanded_globs = expand_globs(context).uniq
expanded_globs = expand_globs(context, pipeline).uniq
return false if expanded_globs.empty?
cache_key = [
@ -43,11 +43,17 @@ module Gitlab
end
end
def expand_globs(context)
def expand_globs(context, pipeline)
return paths unless context
paths.map do |glob|
ExpandVariables.expand_existing(glob, -> { context.variables_hash })
if Feature.enabled?(:expand_nested_variables_in_job_rules_exists_and_changes, pipeline.project)
paths.map do |glob|
expand_value_nested(glob, context)
end
else
paths.map do |glob|
expand_value(glob, context)
end
end
end
@ -70,12 +76,25 @@ module Gitlab
def find_compare_to_sha(pipeline, context)
return unless @globs.include?(:compare_to)
compare_to = ExpandVariables.expand(@globs[:compare_to], -> { context.variables_hash })
compare_to = if Feature.enabled?(:expand_nested_variables_in_job_rules_exists_and_changes, pipeline.project)
expand_value_nested(@globs[:compare_to], context)
else
expand_value(@globs[:compare_to], context)
end
commit = pipeline.project.commit(compare_to)
raise Rules::Rule::Clause::ParseError, 'rules:changes:compare_to is not a valid ref' unless commit
commit.sha
end
def expand_value(value, context)
ExpandVariables.expand_existing(value, -> { context.variables_hash })
end
def expand_value_nested(value, context)
ExpandVariables.expand_existing(value, -> { context.variables_hash_expanded })
end
end
end
end

View File

@ -18,13 +18,13 @@ module Gitlab
@ref = clause[:ref]
end
def satisfied_by?(_pipeline, context)
def satisfied_by?(pipeline, context)
# Return early to avoid redundant Gitaly calls
return false unless @globs.any?
context = change_context(context) if @project_path
context = change_context(context, pipeline) if @project_path
expanded_globs = expand_globs(context)
expanded_globs = expand_globs(context, pipeline)
top_level_only = expanded_globs.all?(&method(:top_level_glob?))
paths = worktree_paths(context, top_level_only)
@ -42,9 +42,15 @@ module Gitlab
grouped.values_at(:exact, :extension, :pattern).map { |globs| Array(globs) }
end
def expand_globs(context)
@globs.map do |glob|
expand_value(glob, context)
def expand_globs(context, pipeline)
if Feature.enabled?(:expand_nested_variables_in_job_rules_exists_and_changes, pipeline&.project)
@globs.map do |glob|
expand_value_nested(glob, context)
end
else
@globs.map do |glob|
expand_value(glob, context)
end
end
end
@ -121,10 +127,10 @@ module Gitlab
glob.delete_prefix(WILDCARD_NESTED_PATTERN)
end
def change_context(old_context)
def change_context(old_context, pipeline)
user = find_context_user(old_context)
new_project = find_context_project(user, old_context)
new_sha = find_context_sha(new_project, old_context)
new_project = find_context_project(user, old_context, pipeline)
new_sha = find_context_sha(new_project, old_context, pipeline)
Gitlab::Ci::Config::External::Context.new(
project: new_project,
@ -138,8 +144,13 @@ module Gitlab
context.is_a?(Gitlab::Ci::Config::External::Context) ? context.user : context.pipeline.user
end
def find_context_project(user, context)
full_path = expand_value(@project_path, context)
def find_context_project(user, context, pipeline)
full_path = if Feature.enabled?(:expand_nested_variables_in_job_rules_exists_and_changes, pipeline.project)
expand_value_nested(@project_path, context)
else
expand_value(@project_path, context)
end
project = Project.find_by_full_path(full_path)
unless project
@ -156,10 +167,16 @@ module Gitlab
project
end
def find_context_sha(project, context)
def find_context_sha(project, context, pipeline)
return project.commit&.sha unless @ref
ref = expand_value(@ref, context)
ref = if Feature.enabled?(:expand_nested_variables_in_job_rules_exists_and_changes,
pipeline.project)
expand_value_nested(@ref, context)
else
expand_value(@ref, context)
end
commit = project.commit(ref)
unless commit
@ -184,6 +201,10 @@ module Gitlab
def expand_value(value, context)
ExpandVariables.expand_existing(value, -> { context.variables_hash })
end
def expand_value_nested(value, context)
ExpandVariables.expand_existing(value, -> { context.variables_hash_expanded })
end
end
end
end

View File

@ -61,6 +61,12 @@ module Gitlab
end
end
def variables_hash_expanded
strong_memoize(:variables_hash_expanded) do
variables.sort_and_expand_all.to_hash
end
end
def mutate(attrs = {})
self.class.new(**attrs) do |ctx|
ctx.pipeline = pipeline

View File

@ -57513,6 +57513,12 @@ msgid_plural "Todos|Marked %d to-dos as done"
msgstr[0] ""
msgstr[1] ""
msgid "Todos|Marked as done"
msgstr ""
msgid "Todos|Marked as undone"
msgstr ""
msgid "Todos|Member access request"
msgstr ""

View File

@ -145,6 +145,7 @@ class ApplicationSettingsAnalysis
help_page_support_url
help_page_text
home_page_url
identity_verification_settings
import_sources
importers
invisible_captcha_enabled
@ -217,6 +218,7 @@ class ApplicationSettingsAnalysis
shared_runners_minutes
shared_runners_text
sidekiq_job_limiter_limit_bytes
sign_in_restrictions
signup_enabled
silent_admin_exports_enabled
slack_app_enabled

View File

@ -612,47 +612,6 @@ RSpec.describe Projects::PipelinesController, feature_category: :continuous_inte
end
end
describe 'GET dag' do
let(:pipeline) { create(:ci_pipeline, project: project) }
it_behaves_like 'the show page', 'dag'
end
describe 'GET dag.json' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build_stage) { create(:ci_stage, name: 'build', pipeline: pipeline) }
let(:test_stage) { create(:ci_stage, name: 'test', pipeline: pipeline) }
before do
create_build(build_stage, 1, 'build')
create_build(test_stage, 2, 'test', scheduling_type: 'dag').tap do |job|
create(:ci_build_need, build: job, name: 'build')
end
end
it 'returns the pipeline with DAG serialization' do
get :dag, params: { namespace_id: project.namespace, project_id: project, id: pipeline }, format: :json
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.fetch('stages')).not_to be_empty
build_stage = json_response['stages'].first
expect(build_stage.fetch('name')).to eq 'build'
expect(build_stage.fetch('groups').first.fetch('jobs'))
.to eq [{ 'name' => 'build', 'scheduling_type' => 'stage' }]
test_stage = json_response['stages'].last
expect(test_stage.fetch('name')).to eq 'test'
expect(test_stage.fetch('groups').first.fetch('jobs'))
.to eq [{ 'name' => 'test', 'scheduling_type' => 'dag', 'needs' => ['build'] }]
end
def create_build(stage, stage_idx, name, params = {})
create(:ci_build, pipeline: pipeline, ci_stage: stage, stage_idx: stage_idx, name: name, **params)
end
end
describe 'GET builds' do
let(:pipeline) { create(:ci_pipeline, project: project) }

View File

@ -4,7 +4,7 @@
require 'spec_helper'
RSpec.describe 'Dashboard > User filters todos', :js, feature_category: :team_planning do
RSpec.describe 'Dashboard > User filters todos', :js, feature_category: :notifications do
let(:user_1) { create(:user, username: 'user_1', name: 'user_1') }
let(:user_2) { create(:user, username: 'user_2', name: 'user_2') }

View File

@ -4,7 +4,7 @@
require 'spec_helper'
RSpec.describe 'Dashboard > User sorts todos', feature_category: :team_planning do
RSpec.describe 'Dashboard > User sorts todos', feature_category: :notifications do
let(:user) { create(:user) }
let(:project) { create(:project) }

View File

@ -4,7 +4,7 @@
require 'spec_helper'
RSpec.describe 'Dashboard Todos', :js, feature_category: :team_planning do
RSpec.describe 'Dashboard Todos', :js, feature_category: :notifications do
include DesignManagementTestHelpers
let_it_be(:user) { create(:user, username: 'john') }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'Manually create a todo item from issue', :js, feature_category: :team_planning do
RSpec.describe 'Manually create a todo item from issue', :js, feature_category: :notifications do
let!(:project) { create(:project) }
let!(:issue) { create(:issue, project: project) }
let!(:user) { create(:user) }

View File

@ -168,15 +168,6 @@ RSpec.describe 'Project active tab', :js, feature_category: :groups_and_projects
it_behaves_like 'page has active sub tab', _('Pipelines')
end
context 'Needs tab' do
before do
visit dag_project_pipeline_path(project, pipeline)
end
it_behaves_like 'page has active tab', _('Build')
it_behaves_like 'page has active sub tab', _('Pipelines')
end
context 'Builds tab' do
before do
visit builds_project_pipeline_path(project, pipeline)

View File

@ -1220,25 +1220,6 @@ RSpec.describe 'Pipeline', :js, feature_category: :continuous_integration do
end
end
describe 'GET /:project/-/pipelines/:id/dag' do
include_context 'pipeline builds'
let_it_be(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
before do
visit dag_project_pipeline_path(project, pipeline)
end
context 'page tabs' do
it 'shows Pipeline and Jobs tabs with link' do
expect(page).to have_link('Pipeline')
expect(page).to have_link('Jobs')
end
end
end
context 'when user sees pipeline flags in a pipeline detail page' do
let_it_be(:project) { create(:project, :repository) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe TodosFinder, feature_category: :team_planning do
RSpec.describe TodosFinder, feature_category: :notifications do
describe '#execute' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }

View File

@ -0,0 +1,63 @@
import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
import TodoItemActions from '~/todos/components/todo_item_actions.vue';
import { TODO_STATE_DONE, TODO_STATE_PENDING } from '~/todos/constants';
describe('TodoItemActions', () => {
let wrapper;
const mockTodo = {
id: 'gid://gitlab/Todo/1',
state: TODO_STATE_PENDING,
};
const createComponent = (props = {}) => {
wrapper = shallowMount(TodoItemActions, {
propsData: {
todo: mockTodo,
...props,
},
provide: {
currentTab: 0,
},
});
};
it('sets correct icon for pending todo action button', () => {
createComponent();
expect(wrapper.findComponent(GlButton).props('icon')).toBe('check');
});
it('sets correct icon for done todo action button', () => {
createComponent({ todo: { ...mockTodo, state: TODO_STATE_DONE } });
expect(wrapper.findComponent(GlButton).props('icon')).toBe('redo');
});
it('sets correct aria-label for pending todo', () => {
createComponent();
expect(wrapper.findComponent(GlButton).attributes('aria-label')).toBe('Mark as done');
});
it('sets correct aria-label for done todo', () => {
createComponent({ todo: { ...mockTodo, state: TODO_STATE_DONE } });
expect(wrapper.findComponent(GlButton).attributes('aria-label')).toBe('Undo');
});
describe('tooltipTitle', () => {
it('returns null when isLoading is true', () => {
createComponent();
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ isLoading: true });
expect(wrapper.vm.tooltipTitle).toBeNull();
});
it('returns "Mark as done" for pending todo', () => {
createComponent();
expect(wrapper.vm.tooltipTitle).toBe('Mark as done');
});
it('returns "Undo" for done todo', () => {
createComponent({ todo: { ...mockTodo, state: TODO_STATE_DONE } });
expect(wrapper.vm.tooltipTitle).toBe('Undo');
});
});
});

View File

@ -1,4 +1,3 @@
// write jest specs in this file, for the component in the todo_item_body.vue file
import { shallowMount } from '@vue/test-utils';
import { GlLink, GlAvatar, GlAvatarLink } from '@gitlab/ui';
import TodoItemBody from '~/todos/components/todo_item_body.vue';

View File

@ -0,0 +1,70 @@
import { shallowMount } from '@vue/test-utils';
import TodoItem from '~/todos/components/todo_item.vue';
import TodoItemTitle from '~/todos/components/todo_item_title.vue';
import TodoItemBody from '~/todos/components/todo_item_body.vue';
import TodoItemTimestamp from '~/todos/components/todo_item_timestamp.vue';
import TodoItemActions from '~/todos/components/todo_item_actions.vue';
import { TODO_STATE_DONE, TODO_STATE_PENDING } from '~/todos/constants';
describe('TodoItem', () => {
let wrapper;
const createComponent = (props = {}) => {
wrapper = shallowMount(TodoItem, {
propsData: {
currentUserId: '1',
todo: {
id: '1',
state: TODO_STATE_PENDING,
targetType: 'Issue',
targetUrl: '/project/issue/1',
},
...props,
},
});
};
it('renders the component', () => {
createComponent();
expect(wrapper.exists()).toBe(true);
});
it('renders TodoItemTitle component', () => {
createComponent();
expect(wrapper.findComponent(TodoItemTitle).exists()).toBe(true);
});
it('renders TodoItemBody component', () => {
createComponent();
expect(wrapper.findComponent(TodoItemBody).exists()).toBe(true);
});
it('renders TodoItemTimestamp component', () => {
createComponent();
expect(wrapper.findComponent(TodoItemTimestamp).exists()).toBe(true);
});
it('renders TodoItemActions component', () => {
createComponent();
expect(wrapper.findComponent(TodoItemActions).exists()).toBe(true);
});
describe('computed properties', () => {
it('isDone returns true when todo state is done', () => {
createComponent({ todo: { state: TODO_STATE_DONE } });
expect(wrapper.vm.isDone).toBe(true);
});
it('isPending returns true when todo state is pending', () => {
createComponent({ todo: { state: TODO_STATE_PENDING } });
expect(wrapper.vm.isPending).toBe(true);
});
});
it('emits change event when TodoItemActions emits change', async () => {
createComponent();
const todoItemActions = wrapper.findComponent(TodoItemActions);
await todoItemActions.vm.$emit('change', '1', true);
expect(wrapper.emitted('change')).toEqual([['1', true]]);
});
});

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Resolvers::TodosResolver, feature_category: :team_planning do
RSpec.describe Resolvers::TodosResolver, feature_category: :notifications do
include GraphqlHelpers
include DesignManagementTestHelpers

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe GitlabSchema.types['Todo'], feature_category: :team_planning do
RSpec.describe GitlabSchema.types['Todo'], feature_category: :notifications do
let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Types::TodoableInterface, feature_category: :team_planning do
RSpec.describe Types::TodoableInterface, feature_category: :notifications do
include GraphqlHelpers
it 'exposes the expected fields' do

View File

@ -137,4 +137,38 @@ RSpec.describe Keeps::Helpers::FileHelper, feature_category: :tooling do
end
end
end
describe '#replace_as_string' do
let(:filename) { 'file.txt' }
let(:new_milestone) { '17.5' }
let(:parsed_file) do
<<~RUBY
# Migration type +class+
# frozen_string_literal: true
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
=begin
This migration adds
a new column to project
=end
class AddColToProjects < Gitlab::Database::Migration[2.2]
milestone #{new_milestone} # Inline comment
def change
add_column :projects, :bool_col, :boolean, default: false, null: false # adds a new column
end
end# Another inline comment
RUBY
end
before do
described_class.def_node_matcher(:milestone_node, '`(send nil? :milestone $(str _) ...)')
end
it 'parses the file as expected' do
expect(helper.replace_as_string(helper.milestone_node, new_milestone)).to eq(parsed_file)
end
end
end

View File

@ -168,4 +168,12 @@ RSpec.describe Gitlab::Ci::Build::Context::Build, feature_category: :pipeline_co
it_behaves_like 'variables collection'
end
describe '#variables_hash_expanded' do
subject { context.variables_hash_expanded }
it { expect(context.variables_hash_expanded).to be_instance_of(ActiveSupport::HashWithIndifferentAccess) }
it_behaves_like 'variables collection'
end
end

View File

@ -158,10 +158,35 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes, feature_category
end
before do
allow(context).to receive(:variables_hash).and_return(variables_hash)
allow(context).to receive(:variables_hash_expanded).and_return(variables_hash)
end
it { is_expected.to be_truthy }
context 'when the variable is nested' do
let(:variables_hash) do
{ 'HELM_DIR' => 'he$SUFFIX', 'SUFFIX' => 'lm' }
end
let(:variables_hash_expanded) do
{ 'HELM_DIR' => 'helm', 'SUFFIX' => 'lm' }
end
before do
allow(context).to receive(:variables_hash_expanded).and_return(variables_hash_expanded)
end
it { is_expected.to be_truthy }
context 'when expand_nested_variables_in_job_rules_exists_and_changes is disabled' do
before do
stub_feature_flags(expand_nested_variables_in_job_rules_exists_and_changes: false)
allow(context).to receive(:variables_hash).and_return(variables_hash)
end
it { is_expected.to be_falsey }
end
end
end
context 'when variable expansion does not match' do
@ -169,7 +194,7 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes, feature_category
let(:modified_paths) { ['path/with/$in/it/file.txt'] }
before do
allow(context).to receive(:variables_hash).and_return({})
allow(context).to receive(:variables_hash_expanded).and_return({})
end
it { is_expected.to be_truthy }
@ -263,10 +288,54 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes, feature_category
let(:pipeline) { build(:ci_pipeline, project: project, ref: 'feature_2', sha: project.commit('feature_2').sha) }
before do
allow(context).to receive(:variables_hash).and_return(variables_hash)
allow(context).to receive(:variables_hash_expanded).and_return(variables_hash)
end
it { is_expected.to be_truthy }
context 'when expand_nested_variables_in_job_rules_exists_and_changes is disabled' do
before do
stub_feature_flags(expand_nested_variables_in_job_rules_exists_and_changes: false)
allow(context).to receive(:variables_hash).and_return(variables_hash)
end
it { is_expected.to be_truthy }
end
context 'when the variable is nested' do
let(:context) { instance_double(Gitlab::Ci::Build::Context::Base) }
let(:variables_hash) do
{ 'FEATURE_BRANCH_NAME_PREFIX' => 'feature_', 'NESTED_REF_VAR' => '${FEATURE_BRANCH_NAME_PREFIX}1' }
end
let(:variables_hash_expanded) do
{ 'FEATURE_BRANCH_NAME_PREFIX' => 'feature_', 'NESTED_REF_VAR' => 'feature_1' }
end
let(:globs) { { paths: ['file2.txt'], compare_to: '$NESTED_REF_VAR' } }
let(:pipeline) do
build(:ci_pipeline, project: project, ref: 'feature_2', sha: project.commit('feature_2').sha)
end
before do
allow(context).to receive(:variables_hash_expanded).and_return(variables_hash_expanded)
end
it { is_expected.to be_truthy }
context 'when expand_nested_variables_in_job_rules_exists_and_changes is disabled' do
before do
stub_feature_flags(expand_nested_variables_in_job_rules_exists_and_changes: false)
allow(context).to receive(:variables_hash).and_return(variables_hash)
end
it 'raises ParseError' do
expect { satisfied_by }.to raise_error(
::Gitlab::Ci::Build::Rules::Rule::Clause::ParseError, 'rules:changes:compare_to is not a valid ref'
)
end
end
end
end
end
end

View File

@ -6,6 +6,7 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists, feature_category:
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :small_repo, files: { 'subdir/my_file.txt' => '' }) }
let_it_be(:other_project) { create(:project, :small_repo, files: { 'file.txt' => '' }) }
let(:pipeline) { instance_double(Ci::Pipeline, project: project, sha: 'sha', user: user) }
let(:variables) do
Gitlab::Ci::Variables::Collection.new([
@ -13,6 +14,7 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists, feature_category:
{ key: 'FILE_TXT', value: 'file.txt' },
{ key: 'FULL_PATH_VALID', value: 'subdir/my_file.txt' },
{ key: 'FULL_PATH_INVALID', value: 'subdir/does_not_exist.txt' },
{ key: 'NESTED_FULL_PATH_VALID', value: '$SUBDIR/my_file.txt' },
{ key: 'NEW_BRANCH', value: 'new_branch' },
{ key: 'MASKED_VAR', value: 'masked_value', masked: true }
])
@ -29,7 +31,7 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists, feature_category:
end
describe '#satisfied_by?' do
subject(:satisfied_by?) { described_class.new(clause).satisfied_by?(nil, context) }
subject(:satisfied_by?) { described_class.new(clause).satisfied_by?(pipeline, context) }
before do
allow(context).to receive(:variables).and_return(variables)
@ -65,6 +67,20 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists, feature_category:
it { is_expected.to be_falsey }
end
context 'when the variable is nested and matches' do
let(:globs) { ['$NESTED_FULL_PATH_VALID'] }
it { is_expected.to be_truthy }
context 'when expand_nested_variables_in_job_rules_exists_and_changes is disabled' do
before do
stub_feature_flags(expand_nested_variables_in_job_rules_exists_and_changes: false)
end
it { is_expected.to be_falsey }
end
end
end
context 'when a file path has a variable' do
@ -114,6 +130,14 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists, feature_category:
let(:globs) { ['$FILE_TXT'] }
it { is_expected.to be_truthy }
context 'when expand_nested_variables_in_job_rules_exists_and_changes is disabled' do
before do
stub_feature_flags(expand_nested_variables_in_job_rules_exists_and_changes: false)
end
it { is_expected.to be_truthy }
end
end
context 'when the project path is invalid' do
@ -135,6 +159,19 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists, feature_category:
"rules:exists:project `invalid/path/subdir` is not a valid project path"
)
end
context 'when expand_nested_variables_in_job_rules_exists_and_changes is disabled' do
before do
stub_feature_flags(expand_nested_variables_in_job_rules_exists_and_changes: false)
end
it 'raises an error' do
expect { satisfied_by? }.to raise_error(
Gitlab::Ci::Build::Rules::Rule::Clause::ParseError,
"rules:exists:project `invalid/path/subdir` is not a valid project path"
)
end
end
end
context 'when the project path contains a masked variable' do
@ -165,6 +202,14 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists, feature_category:
let(:ref) { '$NEW_BRANCH' }
it { is_expected.to be_truthy }
context 'when expand_nested_variables_in_job_rules_exists_and_changes is disabled' do
before do
stub_feature_flags(expand_nested_variables_in_job_rules_exists_and_changes: false)
end
it { is_expected.to be_truthy }
end
end
context 'when the ref is invalid' do
@ -187,6 +232,20 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists, feature_category:
"in project `#{other_project.full_path}`"
)
end
context 'when expand_nested_variables_in_job_rules_exists_and_changes is disabled' do
before do
stub_feature_flags(expand_nested_variables_in_job_rules_exists_and_changes: false)
end
it 'raises an error' do
expect { satisfied_by? }.to raise_error(
Gitlab::Ci::Build::Rules::Rule::Clause::ParseError,
"rules:exists:ref `invalid/ref/new_branch` is not a valid ref " \
"in project `#{other_project.full_path}`"
)
end
end
end
context 'when the ref contains a masked variable' do

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::External::Rules, feature_category: :pipeline_composition do
let(:context) { double(variables_hash: {}) }
let(:rule_hashes) {}
let(:pipeline) { instance_double(Ci::Pipeline, project_id: project.id, sha: 'sha') }
let(:pipeline) { instance_double(Ci::Pipeline, project: project, project_id: project.id, sha: 'sha') }
let_it_be(:project) { create(:project, :custom_repo, files: { 'file.txt' => 'file' }) }
subject(:rules) { described_class.new(rule_hashes) }

View File

@ -77,36 +77,6 @@ RSpec.describe Ci::PipelineCreation::Requests, :clean_gitlab_redis_shared_state,
expect(described_class.pipeline_creating_for_merge_request?(merge_request)).to be_falsey
end
end
context 'when delete_if_all_complete is true' do
context 'when there are only finished creations for the merge request' do
it 'deletes the MR pipeline creations key from Redis' do
request_1 = described_class.start_for_merge_request(merge_request)
request_2 = described_class.start_for_merge_request(merge_request)
described_class.succeeded(request_1)
described_class.failed(request_2)
expect(described_class.pipeline_creating_for_merge_request?(merge_request, delete_if_all_complete: true))
.to be_falsey
expect(read(request_1)).to be_nil
expect(read(request_2)).to be_nil
end
end
context 'when there are unfinished creations for the merge request' do
it 'does not delete the MR pipeline creations key from Redis' do
request_1 = described_class.start_for_merge_request(merge_request)
request_2 = described_class.start_for_merge_request(merge_request)
described_class.start_for_merge_request(merge_request)
described_class.succeeded(request_1)
expect(described_class.pipeline_creating_for_merge_request?(merge_request, delete_if_all_complete: false))
.to be_truthy
expect(read(request_1)).to eq({ 'status' => 'succeeded' })
expect(read(request_2)).to eq({ 'status' => 'in_progress' })
end
end
end
end
describe '.hset' do

View File

@ -277,6 +277,23 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
end
end
describe '#owner' do
subject(:owner) { runner.owner }
context 'when runner does not have creator_id' do
let_it_be(:runner) { create(:ci_runner, :instance) }
it { is_expected.to be_nil }
end
context 'when runner has creator' do
let_it_be(:creator) { create(:user) }
let_it_be(:runner) { create(:ci_runner, :instance, creator: creator) }
it { is_expected.to eq creator }
end
end
describe '.instance_type' do
let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
let!(:project_runner) { create(:ci_runner, :project, projects: [project]) }
@ -1125,8 +1142,8 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
let_it_be(:project1) { create(:project) }
let_it_be(:project2) { create(:project) }
describe '#owner_project' do
subject(:owner_project) { project_runner.owner_project }
describe '#owner' do
subject(:owner) { project_runner.owner }
context 'with project1 as first project associated with runner' do
let_it_be(:project_runner) { create(:ci_runner, :project, projects: [project1, project2]) }
@ -1638,6 +1655,22 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
end
end
end
describe '#owner' do
subject(:owner) { runner.owner }
context 'with runner assigned to child_group' do
let(:runner) { child_group_runner }
it { is_expected.to eq child_group }
end
context 'with runner assigned to top_level_group_runner' do
let(:runner) { top_level_group_runner }
it { is_expected.to eq top_level_group }
end
end
end
describe '#short_sha' do

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Todo, feature_category: :team_planning do
RSpec.describe Todo, feature_category: :notifications do
let(:issue) { create(:issue) }
describe 'relationships' do

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe TodoPolicy, feature_category: :team_planning do
RSpec.describe TodoPolicy, feature_category: :notifications do
using RSpec::Parameterized::TableSyntax
let_it_be(:project) { create(:project) }

View File

@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe 'A Todoable that implements the CurrentUserTodos interface',
feature_category: :team_planning do
feature_category: :notifications do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'Todo Query', feature_category: :team_planning do
RSpec.describe 'Todo Query', feature_category: :notifications do
include GraphqlHelpers
let_it_be(:current_user) { nil }

View File

@ -1,66 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::DagJobEntity do
let_it_be(:request) { double(:request) }
let(:job) { create(:ci_build, name: 'dag_job') }
let(:entity) { described_class.new(job, request: request) }
describe '#as_json' do
subject { entity.as_json }
RSpec.shared_examples "matches schema" do
it "matches schema" do
expect(subject.to_json).to match_schema('entities/dag_job')
end
end
it 'contains the name' do
expect(subject[:name]).to eq 'dag_job'
end
it_behaves_like "matches schema"
context 'when job is stage scheduled' do
it 'contains the name scheduling_type' do
expect(subject[:scheduling_type]).to eq 'stage'
end
it 'does not expose needs' do
expect(subject).not_to include(:needs)
end
it_behaves_like "matches schema"
end
context 'when job is dag scheduled' do
let(:job) { create(:ci_build, scheduling_type: 'dag') }
it 'contains the name scheduling_type' do
expect(subject[:scheduling_type]).to eq 'dag'
end
it_behaves_like "matches schema"
context 'when job has needs' do
let!(:need) { create(:ci_build_need, build: job, name: 'compile') }
it 'exposes the array of needs' do
expect(subject[:needs]).to eq ['compile']
end
it_behaves_like "matches schema"
end
context 'when job has empty needs' do
it 'exposes an empty array of needs' do
expect(subject[:needs]).to eq []
end
it_behaves_like "matches schema"
end
end
end
end

View File

@ -1,66 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::DagJobGroupEntity do
let_it_be(:request) { double(:request) }
let_it_be(:pipeline) { create(:ci_pipeline) }
let_it_be(:stage) { create(:ci_stage, pipeline: pipeline) }
let(:group) { Ci::Group.new(pipeline.project, stage, name: 'test', jobs: jobs) }
let(:entity) { described_class.new(group, request: request) }
describe '#as_json' do
subject { entity.as_json }
context 'when group contains 1 job' do
let(:job) { create(:ci_build, stage_id: stage.id, pipeline: pipeline, name: 'test') }
let(:jobs) { [job] }
it 'exposes a name' do
expect(subject.fetch(:name)).to eq 'test'
end
it 'exposes the size' do
expect(subject.fetch(:size)).to eq 1
end
it 'exposes the jobs' do
exposed_jobs = subject.fetch(:jobs)
expect(exposed_jobs.size).to eq 1
expect(exposed_jobs.first.fetch(:name)).to eq 'test'
end
it 'matches schema' do
expect(subject.to_json).to match_schema('entities/dag_job_group')
end
end
context 'when group contains multiple parallel jobs' do
let(:job_1) { create(:ci_build, stage_id: stage.id, pipeline: pipeline, name: 'test 1/2') }
let(:job_2) { create(:ci_build, stage_id: stage.id, pipeline: pipeline, name: 'test 2/2') }
let(:jobs) { [job_1, job_2] }
it 'exposes a name' do
expect(subject.fetch(:name)).to eq 'test'
end
it 'exposes the size' do
expect(subject.fetch(:size)).to eq 2
end
it 'exposes the jobs' do
exposed_jobs = subject.fetch(:jobs)
expect(exposed_jobs.size).to eq 2
expect(exposed_jobs.first.fetch(:name)).to eq 'test 1/2'
expect(exposed_jobs.last.fetch(:name)).to eq 'test 2/2'
end
it 'matches schema' do
expect(subject.to_json).to match_schema('entities/dag_job_group')
end
end
end
end

View File

@ -1,163 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::DagPipelineEntity do
let_it_be(:request) { double(:request) }
let_it_be(:pipeline) { create(:ci_pipeline) }
let(:entity) { described_class.new(pipeline, request: request) }
describe '#as_json' do
subject { entity.as_json }
RSpec.shared_examples "matches schema" do
it 'matches schema' do
expect(subject.to_json).to match_schema('entities/dag_pipeline')
end
end
context 'when pipeline is empty' do
it 'contains stages' do
expect(subject).to include(:stages)
expect(subject[:stages]).to be_empty
end
it_behaves_like "matches schema"
end
context 'when pipeline has jobs' do
let_it_be(:build_stage) { create(:ci_stage, name: 'build', pipeline: pipeline) }
let_it_be(:test_stage) { create(:ci_stage, name: 'test', pipeline: pipeline) }
let_it_be(:deploy_stage) { create(:ci_stage, name: 'deploy', pipeline: pipeline) }
let!(:build_job) { create(:ci_build, ci_stage: build_stage, pipeline: pipeline) }
let!(:test_job) { create(:ci_build, ci_stage: test_stage, pipeline: pipeline) }
let!(:deploy_job) { create(:ci_build, ci_stage: deploy_stage, pipeline: pipeline) }
it 'contains 3 stages' do
stages = subject[:stages]
expect(stages.size).to eq 3
expect(stages.map { |s| s[:name] }).to contain_exactly('build', 'test', 'deploy')
end
it_behaves_like "matches schema"
end
context 'when pipeline has parallel jobs, DAG needs and GenericCommitStatus' do
let!(:stage_build) { create(:ci_stage, name: 'build', position: 1, pipeline: pipeline) }
let!(:stage_test) { create(:ci_stage, name: 'test', position: 2, pipeline: pipeline) }
let!(:stage_deploy) { create(:ci_stage, name: 'deploy', position: 3, pipeline: pipeline) }
let!(:job_build_1) { create(:ci_build, name: 'build 1', ci_stage: stage_build, pipeline: pipeline) }
let!(:job_build_2) { create(:ci_build, name: 'build 2', ci_stage: stage_build, pipeline: pipeline) }
let!(:commit_status) { create(:generic_commit_status, ci_stage: stage_build, pipeline: pipeline) }
let!(:job_rspec_1) { create(:ci_build, name: 'rspec 1/2', ci_stage: stage_test, pipeline: pipeline) }
let!(:job_rspec_2) { create(:ci_build, name: 'rspec 2/2', ci_stage: stage_test, pipeline: pipeline) }
let!(:job_jest) do
create(:ci_build, name: 'jest', ci_stage: stage_test, scheduling_type: 'dag', pipeline: pipeline)
.tap do |job|
create(:ci_build_need, name: 'build 1', build: job)
end
end
let!(:job_deploy_ruby) do
create(:ci_build, name: 'deploy_ruby', ci_stage: stage_deploy, scheduling_type: 'dag', pipeline: pipeline)
.tap do |job|
create(:ci_build_need, name: 'rspec 1/2', build: job)
create(:ci_build_need, name: 'rspec 2/2', build: job)
end
end
let!(:job_deploy_js) do
create(:ci_build, name: 'deploy_js', ci_stage: stage_deploy, scheduling_type: 'dag', pipeline: pipeline)
.tap do |job|
create(:ci_build_need, name: 'jest', build: job)
end
end
it 'performs the smallest number of queries', :request_store do
log = ActiveRecord::QueryRecorder.new { subject }
# stages, project, builds, build_needs
expect(log.count).to eq 4
end
it 'contains all the data' do
expected_result = {
stages: [
{
name: 'build',
groups: [
{
name: 'build 1', size: 1, jobs: [
{ name: 'build 1', scheduling_type: 'stage' }
]
},
{
name: 'build 2', size: 1, jobs: [
{ name: 'build 2', scheduling_type: 'stage' }
]
},
{
name: 'generic', size: 1, jobs: [
{ name: 'generic', scheduling_type: nil }
]
}
]
},
{
name: 'test',
groups: [
{
name: 'jest', size: 1, jobs: [
{ name: 'jest', scheduling_type: 'dag', needs: ['build 1'] }
]
},
{
name: 'rspec', size: 2, jobs: [
{ name: 'rspec 1/2', scheduling_type: 'stage' },
{ name: 'rspec 2/2', scheduling_type: 'stage' }
]
}
]
},
{
name: 'deploy',
groups: [
{
name: 'deploy_js', size: 1, jobs: [
{ name: 'deploy_js', scheduling_type: 'dag', needs: ['jest'] }
]
},
{
name: 'deploy_ruby', size: 1, jobs: [
{ name: 'deploy_ruby', scheduling_type: 'dag', needs: ['rspec 1/2', 'rspec 2/2'] }
]
}
]
}
]
}
expect(subject.fetch(:stages)).not_to be_empty
expect(subject.fetch(:stages)[0].fetch(:name)).to eq 'build'
expect(subject.fetch(:stages)[0]).to eq expected_result.fetch(:stages)[0]
expect(subject.fetch(:stages)[1].fetch(:name)).to eq 'test'
expect(subject.fetch(:stages)[1]).to eq expected_result.fetch(:stages)[1]
expect(subject.fetch(:stages)[2].fetch(:name)).to eq 'deploy'
expect(subject.fetch(:stages)[2]).to eq expected_result.fetch(:stages)[2]
end
it_behaves_like "matches schema"
end
end
end

View File

@ -1,21 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::DagPipelineSerializer do
describe '#represent' do
subject { described_class.new.represent(pipeline) }
let(:pipeline) { create(:ci_pipeline) }
let!(:job) { create(:ci_build, pipeline: pipeline) }
it 'includes stages' do
expect(subject[:stages]).to be_present
expect(subject[:stages].size).to eq 1
end
it 'matches schema' do
expect(subject.to_json).to match_schema('entities/dag_pipeline')
end
end
end

View File

@ -1,35 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::DagStageEntity do
let_it_be(:pipeline) { create(:ci_pipeline) }
let_it_be(:request) { double(:request) }
let(:stage) { create(:ci_stage, pipeline: pipeline, name: 'test') }
let(:entity) { described_class.new(stage, request: request) }
let!(:job) { create(:ci_build, :success, pipeline: pipeline, stage_id: stage.id) }
describe '#as_json' do
subject { entity.as_json }
it 'contains valid name' do
expect(subject[:name]).to eq 'test'
end
it 'contains the job groups' do
expect(subject).to include :groups
expect(subject[:groups]).not_to be_empty
job_group = subject[:groups].first
expect(job_group[:name]).to eq 'test'
expect(job_group[:size]).to eq 1
expect(job_group[:jobs]).not_to be_empty
end
it "matches schema" do
expect(subject.to_json).to match_schema('entities/dag_stage')
end
end
end

View File

@ -227,7 +227,7 @@ RSpec.describe Ci::CreatePipelineService, feature_category: :pipeline_compositio
script: echo Hello, World!
rules:
- exists:
- $VAR_NESTED # does not match because of https://gitlab.com/gitlab-org/gitlab/-/issues/411344
- $VAR_NESTED
YAML
end
@ -247,7 +247,18 @@ RSpec.describe Ci::CreatePipelineService, feature_category: :pipeline_compositio
it 'creates all relevant jobs' do
expect(pipeline).to be_persisted
expect(build_names).to contain_exactly('job1', 'job2')
expect(build_names).to contain_exactly('job1', 'job2', 'job4')
end
context 'when expand_nested_variables_in_job_rules_exists_and_changes is disabled' do
before do
stub_feature_flags(expand_nested_variables_in_job_rules_exists_and_changes: false)
end
it 'creates all relevant jobs' do
expect(pipeline).to be_persisted
expect(build_names).to contain_exactly('job1', 'job2')
end
end
end
end
@ -808,6 +819,10 @@ RSpec.describe Ci::CreatePipelineService, feature_category: :pipeline_compositio
VALID_BRANCH_NAME: feature_1
FEATURE_BRANCH_NAME_PREFIX: feature_
INVALID_BRANCH_NAME: invalid-branch
VALID_FILENAME: file2.txt
INVALID_FILENAME: file1.txt
VALID_BASENAME: file2
VALID_NESTED_VARIABLE: ${VALID_BASENAME}.txt
job1:
script: exit 0
rules:
@ -857,6 +872,52 @@ RSpec.describe Ci::CreatePipelineService, feature_category: :pipeline_compositio
)
end
end
context 'when paths is defined by a variable' do
let(:compare_to) { '${VALID_BRANCH_NAME}' }
context 'when the variable does not exist' do
let(:changed_file) { '$NON_EXISTENT_VAR' }
it 'does not create job1' do
expect(build_names).to contain_exactly('job2')
end
end
context 'when the variable contains a matching filename' do
let(:changed_file) { '$VALID_FILENAME' }
it 'creates both jobs' do
expect(build_names).to contain_exactly('job1', 'job2')
end
end
context 'when the variable does not contain a matching filename' do
let(:changed_file) { '$INVALID_FILENAME' }
it 'does not create job1' do
expect(build_names).to contain_exactly('job2')
end
end
context 'when the variable is nested and contains a matching filename' do
let(:changed_file) { '$VALID_NESTED_VARIABLE' }
it 'creates both jobs' do
expect(build_names).to contain_exactly('job1', 'job2')
end
context 'when expand_nested_variables_in_job_rules_exists_and_changes is disabled' do
before do
stub_feature_flags(expand_nested_variables_in_job_rules_exists_and_changes: false)
end
it 'does not create job1' do
expect(build_names).to contain_exactly('job2')
end
end
end
end
end
end

Some files were not shown because too many files have changed in this diff Show More