Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
c4db541c1b
commit
23bc19cb73
|
@ -1,4 +1,6 @@
|
|||
<script>
|
||||
import { GlSprintf, GlLink } from '@gitlab/ui';
|
||||
|
||||
import settingsMixin from 'ee_else_ce/pages/projects/shared/permissions/mixins/settings_pannel_mixin';
|
||||
import { s__ } from '~/locale';
|
||||
import projectFeatureSetting from './project_feature_setting.vue';
|
||||
|
@ -19,6 +21,8 @@ export default {
|
|||
projectFeatureSetting,
|
||||
projectFeatureToggle,
|
||||
projectSettingRow,
|
||||
GlSprintf,
|
||||
GlLink,
|
||||
},
|
||||
mixins: [settingsMixin],
|
||||
|
||||
|
@ -67,6 +71,16 @@ export default {
|
|||
required: false,
|
||||
default: '',
|
||||
},
|
||||
lfsObjectsExist: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
lfsObjectsRemovalHelpPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
registryHelpPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
@ -377,6 +391,23 @@ export default {
|
|||
:disabled-input="!repositoryEnabled"
|
||||
name="project[lfs_enabled]"
|
||||
/>
|
||||
<p v-if="!lfsEnabled && lfsObjectsExist">
|
||||
<gl-sprintf
|
||||
:message="
|
||||
s__(
|
||||
'ProjectSettings|LFS objects from this repository are still available to forks. %{linkStart}How do I remove them?%{linkEnd}',
|
||||
)
|
||||
"
|
||||
>
|
||||
<template #link="{ content }">
|
||||
<span class="d-block">
|
||||
<gl-link :href="lfsObjectsRemovalHelpPath" target="_blank">
|
||||
{{ content }}
|
||||
</gl-link>
|
||||
</span>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</p>
|
||||
</project-setting-row>
|
||||
<project-setting-row
|
||||
v-if="packagesAvailable"
|
||||
|
|
|
@ -9,7 +9,8 @@ class ReleasesFinder
|
|||
def execute(preload: true)
|
||||
return Release.none unless Ability.allowed?(@current_user, :read_release, @project)
|
||||
|
||||
releases = @project.releases
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/211988
|
||||
releases = @project.releases.where.not(tag: nil) # rubocop:disable CodeReuse/ActiveRecord
|
||||
releases = releases.preloaded if preload
|
||||
releases.sorted
|
||||
end
|
||||
|
|
|
@ -603,6 +603,8 @@ module ProjectsHelper
|
|||
registryHelpPath: help_page_path('user/packages/container_registry/index'),
|
||||
lfsAvailable: Gitlab.config.lfs.enabled,
|
||||
lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs'),
|
||||
lfsObjectsExist: project.lfs_objects.exists?,
|
||||
lfsObjectsRemovalHelpPath: help_page_path('administration/lfs/manage_large_binaries_with_git_lfs', anchor: 'removing-objects-from-lfs'),
|
||||
pagesAvailable: Gitlab.config.pages.enabled,
|
||||
pagesAccessControlEnabled: Gitlab.config.pages.access_control,
|
||||
pagesAccessControlForced: ::Gitlab::Pages.access_control_is_forced?,
|
||||
|
|
|
@ -15,6 +15,15 @@ module HasRepository
|
|||
|
||||
delegate :base_dir, :disk_path, to: :storage
|
||||
|
||||
class_methods do
|
||||
def pick_repository_storage
|
||||
# We need to ensure application settings are fresh when we pick
|
||||
# a repository storage to use.
|
||||
Gitlab::CurrentSettings.expire_current_application_settings
|
||||
Gitlab::CurrentSettings.pick_repository_storage
|
||||
end
|
||||
end
|
||||
|
||||
def valid_repo?
|
||||
repository.exists?
|
||||
rescue
|
||||
|
|
|
@ -67,10 +67,7 @@ class Project < ApplicationRecord
|
|||
default_value_for :resolve_outdated_diff_discussions, false
|
||||
default_value_for :container_registry_enabled, gitlab_config_features.container_registry
|
||||
default_value_for(:repository_storage) do
|
||||
# We need to ensure application settings are fresh when we pick
|
||||
# a repository storage to use.
|
||||
Gitlab::CurrentSettings.expire_current_application_settings
|
||||
Gitlab::CurrentSettings.pick_repository_storage
|
||||
pick_repository_storage
|
||||
end
|
||||
|
||||
default_value_for(:shared_runners_enabled) { Gitlab::CurrentSettings.shared_runners_enabled }
|
||||
|
|
|
@ -288,8 +288,7 @@ class Snippet < ApplicationRecord
|
|||
end
|
||||
|
||||
def repository_storage
|
||||
snippet_repository&.shard_name ||
|
||||
Gitlab::CurrentSettings.pick_repository_storage
|
||||
snippet_repository&.shard_name || self.class.pick_repository_storage
|
||||
end
|
||||
|
||||
def create_repository
|
||||
|
|
|
@ -77,6 +77,7 @@ module Ci
|
|||
track_exception(error, job, params)
|
||||
error(error.message, :service_unavailable)
|
||||
rescue => error
|
||||
track_exception(error, job, params)
|
||||
error(error.message, :bad_request)
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Filter out Releases with missing tags
|
||||
merge_request: 27716
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add missing track_exception() call to Ci::CreateJobArtifactsService
|
||||
merge_request: 27954
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Ensure freshness of settings with snippet creation
|
||||
merge_request: 27897
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Show object access warning when disabling repo LFS
|
||||
merge_request: 26696
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add namespace_storage_size_limit to application settings
|
||||
merge_request: 27786
|
||||
author:
|
||||
type: added
|
|
@ -1,240 +1,204 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require './spec/support/sidekiq_middleware'
|
||||
require './spec/support/helpers/test_env'
|
||||
|
||||
# Usage:
|
||||
#
|
||||
# Simple invocation always creates a new project:
|
||||
#
|
||||
# FILTER=cycle_analytics SEED_CYCLE_ANALYTICS=1 bundle exec rake db:seed_fu
|
||||
#
|
||||
# Create more issues/MRs:
|
||||
#
|
||||
# CYCLE_ANALYTICS_ISSUE_COUNT=100 FILTER=cycle_analytics SEED_CYCLE_ANALYTICS=1 bundle exec rake db:seed_fu
|
||||
#
|
||||
# Run for an existing project
|
||||
#
|
||||
# CYCLE_ANALYTICS_SEED_PROJECT_ID=10 FILTER=cycle_analytics SEED_CYCLE_ANALYTICS=1 bundle exec rake db:seed_fu
|
||||
|
||||
class Gitlab::Seeder::CycleAnalytics
|
||||
def initialize(project, perf: false)
|
||||
@project = project
|
||||
@user = User.admins.first
|
||||
@issue_count = perf ? 1000 : ENV.fetch('CYCLE_ANALYTICS_ISSUE_COUNT', 5).to_i
|
||||
attr_reader :project, :issues, :merge_requests, :developers
|
||||
|
||||
FLAG = 'SEED_CYCLE_ANALYTICS'
|
||||
PERF_TEST = 'CYCLE_ANALYTICS_PERF_TEST'
|
||||
|
||||
ISSUE_STAGE_MAX_DURATION_IN_HOURS = 72
|
||||
PLAN_STAGE_MAX_DURATION_IN_HOURS = 48
|
||||
CODE_STAGE_MAX_DURATION_IN_HOURS = 72
|
||||
TEST_STAGE_MAX_DURATION_IN_HOURS = 5
|
||||
REVIEW_STAGE_MAX_DURATION_IN_HOURS = 72
|
||||
DEPLOYMENT_MAX_DURATION_IN_HOURS = 48
|
||||
|
||||
def self.seeder_base_on_env(project)
|
||||
if ENV[FLAG]
|
||||
self.new(project: project)
|
||||
elsif ENV[PERF_TEST]
|
||||
self.new(project: project, perf: true)
|
||||
end
|
||||
end
|
||||
|
||||
def seed_metrics!
|
||||
@issue_count.times do |index|
|
||||
# Issue
|
||||
Timecop.travel 5.days.from_now
|
||||
title = "#{FFaker::Product.brand}-#{FFaker::Product.brand}-#{rand(1000)}"
|
||||
issue = Issue.create(project: @project, title: title, author: @user)
|
||||
issue_metrics = issue.metrics
|
||||
|
||||
# Milestones / Labels
|
||||
Timecop.travel 5.days.from_now
|
||||
|
||||
if index.even?
|
||||
issue_metrics.first_associated_with_milestone_at = rand(6..12).hours.from_now
|
||||
else
|
||||
issue_metrics.first_added_to_board_at = rand(6..12).hours.from_now
|
||||
end
|
||||
|
||||
# Commit
|
||||
Timecop.travel 5.days.from_now
|
||||
issue_metrics.first_mentioned_in_commit_at = rand(6..12).hours.from_now
|
||||
|
||||
# MR
|
||||
Timecop.travel 5.days.from_now
|
||||
branch_name = "#{FFaker::Product.brand}-#{FFaker::Product.brand}-#{rand(1000)}"
|
||||
@project.repository.add_branch(@user, branch_name, 'master')
|
||||
merge_request = MergeRequest.create(target_project: @project, source_project: @project, source_branch: branch_name, target_branch: 'master', title: branch_name, author: @user)
|
||||
merge_request_metrics = merge_request.metrics
|
||||
|
||||
# MR closing issues
|
||||
Timecop.travel 5.days.from_now
|
||||
MergeRequestsClosingIssues.create!(issue: issue, merge_request: merge_request)
|
||||
|
||||
# Merge
|
||||
Timecop.travel 5.days.from_now
|
||||
merge_request_metrics.merged_at = rand(6..12).hours.from_now
|
||||
|
||||
# Start build
|
||||
Timecop.travel 5.days.from_now
|
||||
merge_request_metrics.latest_build_started_at = rand(6..12).hours.from_now
|
||||
|
||||
# Finish build
|
||||
Timecop.travel 5.days.from_now
|
||||
merge_request_metrics.latest_build_finished_at = rand(6..12).hours.from_now
|
||||
|
||||
# Deploy to production
|
||||
Timecop.travel 5.days.from_now
|
||||
merge_request_metrics.first_deployed_to_production_at = rand(6..12).hours.from_now
|
||||
|
||||
issue_metrics.save!
|
||||
merge_request_metrics.save!
|
||||
|
||||
print '.'
|
||||
end
|
||||
def initialize(project: nil, perf: false)
|
||||
@project = project || create_new_vsm_project
|
||||
@issue_count = perf ? 1000 : ENV.fetch('CYCLE_ANALYTICS_ISSUE_COUNT', 5).to_i
|
||||
@issues = []
|
||||
@merge_requests = []
|
||||
@developers = []
|
||||
end
|
||||
|
||||
def seed!
|
||||
Sidekiq::Worker.skipping_transaction_check do
|
||||
Sidekiq::Testing.inline! do
|
||||
issues = create_issues
|
||||
puts '.'
|
||||
create_developers!
|
||||
create_issues!
|
||||
|
||||
# Stage 1
|
||||
Timecop.travel 5.days.from_now
|
||||
add_milestones_and_list_labels(issues)
|
||||
print '.'
|
||||
seed_issue_stage!
|
||||
seed_plan_stage!
|
||||
seed_code_stage!
|
||||
seed_test_stage!
|
||||
seed_review_stage!
|
||||
seed_staging_stage!
|
||||
|
||||
# Stage 2
|
||||
Timecop.travel 5.days.from_now
|
||||
branches = mention_in_commits(issues)
|
||||
print '.'
|
||||
|
||||
# Stage 3
|
||||
Timecop.travel 5.days.from_now
|
||||
merge_requests = create_merge_requests_closing_issues(issues, branches)
|
||||
print '.'
|
||||
|
||||
# Stage 4
|
||||
Timecop.travel 5.days.from_now
|
||||
run_builds(merge_requests)
|
||||
print '.'
|
||||
|
||||
# Stage 5
|
||||
Timecop.travel 5.days.from_now
|
||||
merge_merge_requests(merge_requests)
|
||||
print '.'
|
||||
|
||||
# Stage 6 / 7
|
||||
Timecop.travel 5.days.from_now
|
||||
deploy_to_production(merge_requests)
|
||||
print '.'
|
||||
end
|
||||
end
|
||||
|
||||
print '.'
|
||||
puts "Successfully seeded '#{project.full_path}' for Value Stream Management!"
|
||||
puts "URL: #{Rails.application.routes.url_helpers.project_url(project)}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_issues
|
||||
Array.new(@issue_count) do
|
||||
issue_params = {
|
||||
title: "Value Stream Analytics: #{FFaker::Lorem.sentence(6)}",
|
||||
description: FFaker::Lorem.sentence,
|
||||
state: 'opened',
|
||||
assignees: [@project.team.users.sample]
|
||||
}
|
||||
def seed_issue_stage!
|
||||
issues.each do |issue|
|
||||
time = within_end_time(issue.created_at + rand(ISSUE_STAGE_MAX_DURATION_IN_HOURS).hours)
|
||||
|
||||
Issues::CreateService.new(@project, @project.team.users.sample, issue_params).execute
|
||||
end
|
||||
end
|
||||
|
||||
def add_milestones_and_list_labels(issues)
|
||||
issues.shuffle.map.with_index do |issue, index|
|
||||
Timecop.travel 12.hours.from_now
|
||||
|
||||
if index.even?
|
||||
issue.update(milestone: @project.milestones.sample)
|
||||
if issue.id.even?
|
||||
issue.metrics.update!(first_associated_with_milestone_at: time)
|
||||
else
|
||||
label_name = "#{FFaker::Product.brand}-#{FFaker::Product.brand}-#{rand(1000)}"
|
||||
list_label = FactoryBot.create(:label, title: label_name, project: issue.project)
|
||||
FactoryBot.create(:list, board: FactoryBot.create(:board, project: issue.project), label: list_label)
|
||||
issue.update(labels: [list_label])
|
||||
issue.metrics.update!(first_added_to_board_at: time)
|
||||
end
|
||||
|
||||
issue
|
||||
end
|
||||
end
|
||||
|
||||
def mention_in_commits(issues)
|
||||
issues.map do |issue|
|
||||
Timecop.travel 12.hours.from_now
|
||||
def seed_plan_stage!
|
||||
issues.each do |issue|
|
||||
plan_stage_start = issue.metrics.first_associated_with_milestone_at || issue.metrics.first_added_to_board_at
|
||||
|
||||
branch_name = filename = "#{FFaker::Product.brand}-#{FFaker::Product.brand}-#{rand(1000)}"
|
||||
|
||||
issue.project.repository.add_branch(@user, branch_name, 'master')
|
||||
|
||||
commit_sha = issue.project.repository.create_file(@user, filename, "content", message: "Commit for #{issue.to_reference}", branch_name: branch_name)
|
||||
issue.project.repository.commit(commit_sha)
|
||||
|
||||
::Git::BranchPushService.new(
|
||||
issue.project,
|
||||
@user,
|
||||
change: {
|
||||
oldrev: issue.project.repository.commit("master").sha,
|
||||
newrev: commit_sha,
|
||||
ref: 'refs/heads/master'
|
||||
}
|
||||
).execute
|
||||
|
||||
branch_name
|
||||
first_mentioned_in_commit_at = within_end_time(plan_stage_start + rand(PLAN_STAGE_MAX_DURATION_IN_HOURS).hours)
|
||||
issue.metrics.update!(first_mentioned_in_commit_at: first_mentioned_in_commit_at)
|
||||
end
|
||||
end
|
||||
|
||||
def create_merge_requests_closing_issues(issues, branches)
|
||||
issues.zip(branches).map do |issue, branch|
|
||||
Timecop.travel 12.hours.from_now
|
||||
def seed_code_stage!
|
||||
issues.each do |issue|
|
||||
merge_request = FactoryBot.create(
|
||||
:merge_request,
|
||||
target_project: project,
|
||||
source_project: project,
|
||||
source_branch: "#{issue.iid}-feature-branch",
|
||||
target_branch: 'master',
|
||||
author: developers.sample,
|
||||
created_at: within_end_time(issue.metrics.first_mentioned_in_commit_at + rand(CODE_STAGE_MAX_DURATION_IN_HOURS).hours)
|
||||
)
|
||||
|
||||
opts = {
|
||||
title: 'Value Stream Analytics merge_request',
|
||||
description: "Fixes #{issue.to_reference}",
|
||||
source_branch: branch,
|
||||
target_branch: 'master'
|
||||
}
|
||||
@merge_requests << merge_request
|
||||
|
||||
MergeRequests::CreateService.new(issue.project, @user, opts).execute
|
||||
MergeRequestsClosingIssues.create!(issue: issue, merge_request: merge_request)
|
||||
end
|
||||
end
|
||||
|
||||
def run_builds(merge_requests)
|
||||
def seed_test_stage!
|
||||
merge_requests.each do |merge_request|
|
||||
Timecop.travel 12.hours.from_now
|
||||
pipeline = FactoryBot.create(:ci_pipeline, :success, project: project)
|
||||
build = FactoryBot.create(:ci_build, pipeline: pipeline, project: project, user: developers.sample)
|
||||
|
||||
service = Ci::CreatePipelineService.new(merge_request.project,
|
||||
@user,
|
||||
ref: "refs/heads/#{merge_request.source_branch}")
|
||||
pipeline = service.execute(:push, ignore_skip_ci: true, save_on_errors: false)
|
||||
|
||||
pipeline.builds.each(&:enqueue) # make sure all pipelines in pending state
|
||||
pipeline.builds.each(&:run!)
|
||||
pipeline.update_legacy_status
|
||||
merge_request.metrics.update!(
|
||||
latest_build_started_at: merge_request.created_at,
|
||||
latest_build_finished_at: within_end_time(merge_request.created_at + TEST_STAGE_MAX_DURATION_IN_HOURS.hours),
|
||||
pipeline_id: build.commit_id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def merge_merge_requests(merge_requests)
|
||||
def seed_review_stage!
|
||||
merge_requests.each do |merge_request|
|
||||
Timecop.travel 12.hours.from_now
|
||||
|
||||
MergeRequests::MergeService.new(merge_request.project, @user).execute(merge_request)
|
||||
merge_request.metrics.update!(merged_at: within_end_time(merge_request.created_at + REVIEW_STAGE_MAX_DURATION_IN_HOURS.hours))
|
||||
end
|
||||
end
|
||||
|
||||
def deploy_to_production(merge_requests)
|
||||
def seed_staging_stage!
|
||||
merge_requests.each do |merge_request|
|
||||
next unless merge_request.head_pipeline
|
||||
|
||||
Timecop.travel 12.hours.from_now
|
||||
|
||||
job = merge_request.head_pipeline.builds.where.not(environment: nil).last
|
||||
|
||||
job.success!
|
||||
job.pipeline.update_legacy_status
|
||||
merge_request.metrics.update!(first_deployed_to_production_at: within_end_time(merge_request.metrics.merged_at + DEPLOYMENT_MAX_DURATION_IN_HOURS.hours))
|
||||
end
|
||||
end
|
||||
|
||||
def create_issues!
|
||||
@issue_count.times do
|
||||
Timecop.travel start_time + rand(5).days do
|
||||
title = "#{FFaker::Product.brand}-#{suffix}"
|
||||
@issues << Issue.create!(project: project, title: title, author: developers.sample)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create_developers!
|
||||
5.times do |i|
|
||||
user = FactoryBot.create(
|
||||
:user,
|
||||
name: "VSM User#{i}",
|
||||
username: "vsm-user-#{i}-#{suffix}",
|
||||
email: "vsm-user-#{i}@#{suffix}.com"
|
||||
)
|
||||
|
||||
project.group.add_developer(user)
|
||||
project.add_developer(user)
|
||||
|
||||
@developers << user
|
||||
end
|
||||
end
|
||||
|
||||
def create_new_vsm_project
|
||||
project = FactoryBot.create(
|
||||
:project,
|
||||
name: "Value Stream Management Project #{suffix}",
|
||||
path: "vsmp-#{suffix}",
|
||||
creator: admin,
|
||||
namespace: FactoryBot.create(
|
||||
:group,
|
||||
name: "Value Stream Management Group (#{suffix})",
|
||||
path: "vsmg-#{suffix}"
|
||||
)
|
||||
)
|
||||
|
||||
project.create_repository
|
||||
project
|
||||
end
|
||||
|
||||
def admin
|
||||
@admin ||= User.admins.first
|
||||
end
|
||||
|
||||
def suffix
|
||||
@suffix ||= Time.now.to_i
|
||||
end
|
||||
|
||||
def start_time
|
||||
@start_time ||= 25.days.ago
|
||||
end
|
||||
|
||||
def end_time
|
||||
@end_time ||= Time.now
|
||||
end
|
||||
|
||||
def within_end_time(time)
|
||||
[time, end_time].min
|
||||
end
|
||||
end
|
||||
|
||||
Gitlab::Seeder.quiet do
|
||||
flag = 'SEED_CYCLE_ANALYTICS'
|
||||
project_id = ENV['CYCLE_ANALYTICS_SEED_PROJECT_ID']
|
||||
project = Project.find(project_id) if project_id
|
||||
|
||||
if ENV[flag]
|
||||
Project.not_mass_generated.find_each do |project|
|
||||
# This seed naively assumes that every project has a repository, and every
|
||||
# repository has a `master` branch, which may be the case for a pristine
|
||||
# GDK seed, but is almost never true for a GDK that's actually had
|
||||
# development performed on it.
|
||||
next unless project.repository_exists?
|
||||
next unless project.repository.commit('master')
|
||||
seeder = Gitlab::Seeder::CycleAnalytics.seeder_base_on_env(project)
|
||||
|
||||
seeder = Gitlab::Seeder::CycleAnalytics.new(project)
|
||||
seeder.seed!
|
||||
end
|
||||
elsif ENV['CYCLE_ANALYTICS_PERF_TEST']
|
||||
seeder = Gitlab::Seeder::CycleAnalytics.new(Project.order(:id).first, perf: true)
|
||||
if seeder
|
||||
seeder.seed!
|
||||
elsif ENV['CYCLE_ANALYTICS_POPULATE_METRICS_DIRECTLY']
|
||||
seeder = Gitlab::Seeder::CycleAnalytics.new(Project.order(:id).first, perf: true)
|
||||
seeder.seed_metrics!
|
||||
else
|
||||
puts "Skipped. Use the `#{flag}` environment variable to enable."
|
||||
puts "Skipped. Use the `#{Gitlab::Seeder::CycleAnalytics::FLAG}` environment variable to enable."
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddNamespaceStorageSizeLimitToApplicationSettings < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_column_with_default :application_settings, :namespace_storage_size_limit, :bigint, default: 0
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :application_settings, :namespace_storage_size_limit
|
||||
end
|
||||
end
|
|
@ -395,7 +395,8 @@ CREATE TABLE public.application_settings (
|
|||
prevent_merge_requests_committers_approval boolean DEFAULT false NOT NULL,
|
||||
email_restrictions_enabled boolean DEFAULT false NOT NULL,
|
||||
email_restrictions text,
|
||||
npm_package_requests_forwarding boolean DEFAULT true NOT NULL
|
||||
npm_package_requests_forwarding boolean DEFAULT true NOT NULL,
|
||||
namespace_storage_size_limit bigint DEFAULT 0 NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE public.application_settings_id_seq
|
||||
|
@ -12750,5 +12751,6 @@ INSERT INTO "schema_migrations" (version) VALUES
|
|||
('20200319123041'),
|
||||
('20200319203901'),
|
||||
('20200323075043'),
|
||||
('20200323122201');
|
||||
('20200323122201'),
|
||||
('20200324115359');
|
||||
|
||||
|
|
|
@ -314,6 +314,19 @@ It is important to note that selective synchronization:
|
|||
Selective synchronization restrictions are implemented on the **secondary** nodes,
|
||||
not the **primary** node.
|
||||
|
||||
### Git operations on unreplicated respositories
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2562) in GitLab 12.10.
|
||||
|
||||
Git clone, pull, and push operations over HTTP(S) are supported for repositories that
|
||||
exist on the **primary** node but not on **secondary** nodes. This situation can occur
|
||||
when:
|
||||
|
||||
- Selective synchronization does not include the project attached to the repository.
|
||||
- The repository is actively being replicated but has not completed yet.
|
||||
|
||||
SSH [support is planned](https://gitlab.com/groups/gitlab-org/-/epics/2562).
|
||||
|
||||
## Upgrading Geo
|
||||
|
||||
See the [updating the Geo nodes document](updating_the_geo_nodes.md).
|
||||
|
|
|
@ -244,6 +244,7 @@ CAUTION: **Caution:**
|
|||
This list of limitations only reflects the latest version of GitLab. If you are using an older version, extra limitations may be in place.
|
||||
|
||||
- Pushing directly to a **secondary** node redirects (for HTTP) or proxies (for SSH) the request to the **primary** node instead of [handling it directly](https://gitlab.com/gitlab-org/gitlab/issues/1381), except when using Git over HTTP with credentials embedded within the URI. For example, `https://user:password@secondary.tld`.
|
||||
- Cloning, pulling, or pushing repositories that exist on the **primary** node but not on the **secondary** nodes where [selective synchronization](configuration.md#selective-synchronization) does not include the project is not supported over SSH [but support is planned](https://gitlab.com/groups/gitlab-org/-/epics/2562). HTTP(S) is supported.
|
||||
- The **primary** node has to be online for OAuth login to happen. Existing sessions and Git are not affected.
|
||||
- The installation takes multiple manual steps that together can take about an hour depending on circumstances. We are working on improving this experience. See [Omnibus GitLab issue #2978](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2978) for details.
|
||||
- Real-time updates of issues/merge requests (for example, via long polling) doesn't work on the **secondary** node.
|
||||
|
|
|
@ -93,6 +93,13 @@ git lfs fetch origin master
|
|||
|
||||
Read the documentation on how to [migrate an existing Git repo with Git LFS](../../topics/git/migrate_to_git_lfs/index.md).
|
||||
|
||||
### Removing objects from LFS
|
||||
|
||||
To remove objects from LFS:
|
||||
|
||||
1. Use [BFG-Cleaner](../../user/project/repository/reducing_the_repo_size_using_git.md#using-the-bfg-repo-cleaner) or [filter-branch](../../user/project/repository/reducing_the_repo_size_using_git.md#using-git-filter-branch) to remove the objects from the repository.
|
||||
1. Delete the relevant LFS lines for the objects you have removed from your `.gitattributes` file and commit those changes.
|
||||
|
||||
## File Locking
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/issues/35856) in GitLab 10.5.
|
||||
|
|
|
@ -94,3 +94,5 @@ Note the following:
|
|||
to allow project imports into the desired group structure.
|
||||
- Imported groups are given a `private` visibility level, unless imported into a parent group.
|
||||
- If imported into a parent group, subgroups will inherit a similar level of visibility, unless otherwise restricted.
|
||||
- To preserve the member list and their respective permissions on imported groups,
|
||||
review the users in these groups. Make sure these users exist before importing the desired groups.
|
||||
|
|
|
@ -14,7 +14,7 @@ see fit.
|
|||
|
||||
## Issue triaging
|
||||
|
||||
Our issue triage policies are [described in our handbook](https://about.gitlab.com/handbook/engineering/issue-triage/).
|
||||
Our issue triage policies are [described in our handbook](https://about.gitlab.com/handbook/engineering/quality/issue-triage/).
|
||||
You are very welcome to help the GitLab team triage issues.
|
||||
We also organize [issue bash events](https://gitlab.com/gitlab-org/gitlab-foss/issues/17815)
|
||||
once every quarter.
|
||||
|
|
|
@ -45,13 +45,6 @@ A database **reviewer**'s role is to:
|
|||
reassign MR to the database **maintainer** suggested by Reviewer
|
||||
Roulette.
|
||||
|
||||
#### When there are no database maintainers available
|
||||
|
||||
Currently we have a [critical shortage of database maintainers](https://gitlab.com/gitlab-org/gitlab/issues/29717). Until we are able to increase the number of database maintainers to support the volume of reviews, we have implemented this temporary solution. If the database **reviewer** cannot find an available database **maintainer** then:
|
||||
|
||||
1. Assign the MR for a second review by a **database trainee maintainer** for further review.
|
||||
1. Once satisfied with the review process and if the database **maintainer** is still not available, skip the database maintainer approval step and assign the merge request to a backend maintainer for final review and approval.
|
||||
|
||||
A database **maintainer**'s role is to:
|
||||
|
||||
- Perform the final database review on the MR.
|
||||
|
|
|
@ -972,6 +972,7 @@ The following are examples of source Markdown for menu items with their publishe
|
|||
1. Go to **{book}** **Wiki**
|
||||
1. Go to **{snippet}** **Snippets**
|
||||
1. Go to **{users}** **Members**
|
||||
1. Select the **More actions** **{ellipsis_v}** icon > **Hide stage**
|
||||
```
|
||||
|
||||
1. Go to **{home}** **Project overview > Details**
|
||||
|
@ -986,6 +987,7 @@ The following are examples of source Markdown for menu items with their publishe
|
|||
1. Go to **{book}** **Wiki**
|
||||
1. Go to **{snippet}** **Snippets**
|
||||
1. Go to **{users}** **Members**
|
||||
1. Select the **More actions** **{ellipsis_v}** icon > **Hide stage**
|
||||
|
||||
## Alert boxes
|
||||
|
||||
|
@ -1364,17 +1366,18 @@ NOTE: **Note:**
|
|||
The [Product Manager for the relevant group](https://about.gitlab.com/handbook/product/categories/#devops-stages)
|
||||
must review and approve the addition or removal of any mentions of using feature flags before the doc change is merged.
|
||||
|
||||
The following is sample text for adding feature flag documentation for a feature:
|
||||
The following is sample text for adding feature flag documentation for a feature that is
|
||||
off by default:
|
||||
|
||||
````md
|
||||
### Disabling the feature
|
||||
### Enabling the feature
|
||||
|
||||
This feature comes with the `:feature_flag` feature flag enabled by default. However, in some cases
|
||||
this feature is incompatible with old configuration. To turn off the feature while configuration is
|
||||
migrated, ask a GitLab administrator with Rails console access to run the following command:
|
||||
This feature comes with the `:feature_flag` feature flag disabled by default. In some cases,
|
||||
this feature is incompatible with an old configuration. To turn on the feature,
|
||||
ask a GitLab administrator with Rails console access to run the following command:
|
||||
|
||||
```ruby
|
||||
Feature.disable(:feature_flag)
|
||||
Feature.enable(:feature_flag)
|
||||
```
|
||||
````
|
||||
|
||||
|
|
|
@ -85,6 +85,7 @@ The following relate to Git Large File Storage:
|
|||
|
||||
- [Getting Started with Git LFS](https://about.gitlab.com/blog/2017/01/30/getting-started-with-git-lfs-tutorial/)
|
||||
- [Migrate an existing Git repo with Git LFS](migrate_to_git_lfs/index.md)
|
||||
- [Removing objects from LFS](../../administration/lfs/manage_large_binaries_with_git_lfs.md#removing-objects-from-lfs)
|
||||
- [GitLab Git LFS user documentation](../../administration/lfs/manage_large_binaries_with_git_lfs.md)
|
||||
- [GitLab Git LFS admin documentation](../../administration/lfs/lfs_administration.md)
|
||||
- [git-annex to Git-LFS migration guide](../../administration/lfs/migrate_from_git_annex_to_git_lfs.md)
|
||||
|
|
|
@ -32,8 +32,8 @@ module Gitlab
|
|||
MAX_ALLOWED_LOOPS = 10_000
|
||||
SLEEP_TIME_IN_SECONDS = 0.01 # 10 msec sleep
|
||||
# Each query should take <<500ms https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22705
|
||||
DEFAULT_DISTINCT_BATCH_SIZE = 100_000
|
||||
DEFAULT_BATCH_SIZE = 10_000
|
||||
DEFAULT_DISTINCT_BATCH_SIZE = 10_000
|
||||
DEFAULT_BATCH_SIZE = 100_000
|
||||
|
||||
def initialize(relation, column: nil)
|
||||
@relation = relation
|
||||
|
@ -51,7 +51,7 @@ module Gitlab
|
|||
raise "The mode #{mode.inspect} is not supported" unless [:itself, :distinct].include?(mode)
|
||||
|
||||
# non-distinct have better performance
|
||||
batch_size ||= mode == :distinct ? DEFAULT_BATCH_SIZE : DEFAULT_DISTINCT_BATCH_SIZE
|
||||
batch_size ||= mode == :distinct ? DEFAULT_DISTINCT_BATCH_SIZE : DEFAULT_BATCH_SIZE
|
||||
|
||||
start = @relation.minimum(@column) || 0
|
||||
finish = @relation.maximum(@column) || 0
|
||||
|
|
|
@ -15552,6 +15552,9 @@ msgstr ""
|
|||
msgid "ProjectSettings|Issues"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|LFS objects from this repository are still available to forks. %{linkStart}How do I remove them?%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Learn more about badges."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -52,6 +52,18 @@ describe ReleasesFinder do
|
|||
subject
|
||||
end
|
||||
end
|
||||
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27716
|
||||
context 'when tag is nil' do
|
||||
before do
|
||||
v1_0_0.update_column(:tag, nil)
|
||||
end
|
||||
|
||||
it 'ignores rows with a nil tag' do
|
||||
expect(subject.size).to eq(1)
|
||||
expect(subject).to eq([v1_1_0])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { shallowMount, mount } from '@vue/test-utils';
|
||||
|
||||
import settingsPanel from '~/pages/projects/shared/permissions/components/settings_panel.vue';
|
||||
import {
|
||||
|
@ -32,6 +32,8 @@ const defaultProps = {
|
|||
registryHelpPath: '/help/user/packages/container_registry/index',
|
||||
lfsAvailable: true,
|
||||
lfsHelpPath: '/help/workflow/lfs/manage_large_binaries_with_git_lfs',
|
||||
lfsObjectsExist: false,
|
||||
lfsObjectsRemovalHelpPath: `/help/administration/lfs/manage_large_binaries_with_git_lfs#removing-objects-from-lfs`,
|
||||
pagesAvailable: true,
|
||||
pagesAccessControlEnabled: false,
|
||||
pagesAccessControlForced: false,
|
||||
|
@ -43,21 +45,25 @@ const defaultProps = {
|
|||
describe('Settings Panel', () => {
|
||||
let wrapper;
|
||||
|
||||
const mountComponent = customProps => {
|
||||
const propsData = { ...defaultProps, ...customProps };
|
||||
return shallowMount(settingsPanel, { propsData });
|
||||
const mountComponent = (
|
||||
{ currentSettings = {}, ...customProps } = {},
|
||||
mountFn = shallowMount,
|
||||
) => {
|
||||
const propsData = {
|
||||
...defaultProps,
|
||||
...customProps,
|
||||
currentSettings: { ...defaultProps.currentSettings, ...currentSettings },
|
||||
};
|
||||
|
||||
return mountFn(settingsPanel, { propsData });
|
||||
};
|
||||
|
||||
const overrideCurrentSettings = (currentSettingsProps, extraProps = {}) => {
|
||||
return mountComponent({
|
||||
...extraProps,
|
||||
currentSettings: {
|
||||
...defaultProps.currentSettings,
|
||||
...currentSettingsProps,
|
||||
},
|
||||
});
|
||||
return mountComponent({ ...extraProps, currentSettings: currentSettingsProps });
|
||||
};
|
||||
|
||||
const findLFSSettingsMessage = () => wrapper.find({ ref: 'git-lfs-settings' }).find('p');
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = mountComponent();
|
||||
});
|
||||
|
@ -333,6 +339,40 @@ describe('Settings Panel', () => {
|
|||
|
||||
expect(wrapper.find('[name="project[lfs_enabled]"]').props().disabledInput).toEqual(true);
|
||||
});
|
||||
|
||||
describe.each`
|
||||
lfsObjectsExist | lfsEnabled | isShown
|
||||
${true} | ${true} | ${false}
|
||||
${true} | ${false} | ${true}
|
||||
${false} | ${true} | ${false}
|
||||
${false} | ${false} | ${false}
|
||||
`(
|
||||
'with (lfsObjectsExist = $lfsObjectsExist, lfsEnabled = $lfsEnabled)',
|
||||
({ lfsObjectsExist, lfsEnabled, isShown }) => {
|
||||
beforeEach(() => {
|
||||
wrapper = mountComponent({ lfsObjectsExist, currentSettings: { lfsEnabled } }, mount);
|
||||
});
|
||||
|
||||
if (isShown) {
|
||||
it('shows warning message', () => {
|
||||
const message = findLFSSettingsMessage();
|
||||
const link = message.find('a');
|
||||
|
||||
expect(message.text()).toContain(
|
||||
'LFS objects from this repository are still available to forks',
|
||||
);
|
||||
expect(link.text()).toEqual('How do I remove them?');
|
||||
expect(link.attributes('href')).toEqual(
|
||||
'/help/administration/lfs/manage_large_binaries_with_git_lfs#removing-objects-from-lfs',
|
||||
);
|
||||
});
|
||||
} else {
|
||||
it('does not show warning message', () => {
|
||||
expect(findLFSSettingsMessage().exists()).toEqual(false);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('Packages', () => {
|
||||
|
|
|
@ -1391,35 +1391,14 @@ describe Project do
|
|||
context 'repository storage by default' do
|
||||
let(:project) { build(:project) }
|
||||
|
||||
before do
|
||||
storages = {
|
||||
'default' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/repositories'),
|
||||
'picked' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/repositories')
|
||||
}
|
||||
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
|
||||
end
|
||||
|
||||
it 'picks storage from ApplicationSetting' do
|
||||
expect_any_instance_of(ApplicationSetting).to receive(:pick_repository_storage).and_return('picked')
|
||||
expect_next_instance_of(ApplicationSetting) do |instance|
|
||||
expect(instance).to receive(:pick_repository_storage).and_return('picked')
|
||||
end
|
||||
expect(described_class).to receive(:pick_repository_storage).and_call_original
|
||||
|
||||
expect(project.repository_storage).to eq('picked')
|
||||
end
|
||||
|
||||
it 'picks from the latest available storage', :request_store do
|
||||
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
|
||||
Gitlab::CurrentSettings.current_application_settings
|
||||
|
||||
settings = ApplicationSetting.last
|
||||
settings.repository_storages = %w(picked)
|
||||
settings.save!
|
||||
|
||||
expect(Gitlab::CurrentSettings.repository_storages).to eq(%w(default))
|
||||
|
||||
project
|
||||
|
||||
expect(project.repository.storage).to eq('picked')
|
||||
expect(Gitlab::CurrentSettings.repository_storages).to eq(%w(picked))
|
||||
end
|
||||
end
|
||||
|
||||
context 'shared runners by default' do
|
||||
|
|
|
@ -655,10 +655,18 @@ describe Snippet do
|
|||
describe '#repository_storage' do
|
||||
let(:snippet) { create(:snippet) }
|
||||
|
||||
it 'returns default repository storage' do
|
||||
expect(Gitlab::CurrentSettings).to receive(:pick_repository_storage)
|
||||
subject { snippet.repository_storage }
|
||||
|
||||
snippet.repository_storage
|
||||
before do
|
||||
expect_next_instance_of(ApplicationSetting) do |instance|
|
||||
expect(instance).to receive(:pick_repository_storage).and_return('picked')
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns repository storage from ApplicationSetting' do
|
||||
expect(described_class).to receive(:pick_repository_storage).and_call_original
|
||||
|
||||
expect(subject).to eq 'picked'
|
||||
end
|
||||
|
||||
context 'when snippet_project is already created' do
|
||||
|
@ -669,9 +677,7 @@ describe Snippet do
|
|||
end
|
||||
|
||||
it 'returns repository_storage from snippet_project' do
|
||||
expect(Gitlab::CurrentSettings).not_to receive(:pick_repository_storage)
|
||||
|
||||
expect(snippet.repository_storage).to eq 'foo'
|
||||
expect(subject).to eq 'foo'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -168,4 +168,37 @@ RSpec.shared_examples 'model with repository' do
|
|||
it { is_expected.to respond_to(:base_dir) }
|
||||
it { is_expected.to respond_to(:disk_path) }
|
||||
end
|
||||
|
||||
describe '.pick_repository_storage' do
|
||||
subject { described_class.pick_repository_storage }
|
||||
|
||||
before do
|
||||
storages = {
|
||||
'default' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/repositories'),
|
||||
'picked' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/repositories')
|
||||
}
|
||||
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
|
||||
end
|
||||
|
||||
it 'picks storage from ApplicationSetting' do
|
||||
expect_next_instance_of(ApplicationSetting) do |instance|
|
||||
expect(instance).to receive(:pick_repository_storage).and_return('picked')
|
||||
end
|
||||
|
||||
expect(subject).to eq('picked')
|
||||
end
|
||||
|
||||
it 'picks from the latest available storage', :request_store do
|
||||
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
|
||||
Gitlab::CurrentSettings.current_application_settings
|
||||
|
||||
settings = ApplicationSetting.last
|
||||
settings.repository_storages = %w(picked)
|
||||
settings.save!
|
||||
|
||||
expect(Gitlab::CurrentSettings.repository_storages).to eq(%w(default))
|
||||
expect(subject).to eq('picked')
|
||||
expect(Gitlab::CurrentSettings.repository_storages).to eq(%w(picked))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue