Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
9cf7b70ac7
commit
cc77bdd6f5
|
|
@ -5068,7 +5068,6 @@ RSpec/MissingFeatureCategory:
|
|||
- 'spec/policies/ci/bridge_policy_spec.rb'
|
||||
- 'spec/policies/ci/build_policy_spec.rb'
|
||||
- 'spec/policies/ci/pipeline_policy_spec.rb'
|
||||
- 'spec/policies/ci/pipeline_schedule_policy_spec.rb'
|
||||
- 'spec/policies/ci/trigger_policy_spec.rb'
|
||||
- 'spec/policies/clusters/agent_policy_spec.rb'
|
||||
- 'spec/policies/clusters/agent_token_policy_spec.rb'
|
||||
|
|
|
|||
81
CHANGELOG.md
81
CHANGELOG.md
|
|
@ -2,6 +2,37 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 16.2.2 (2023-08-01)
|
||||
|
||||
### Added (1 change)
|
||||
|
||||
- [Add MR reviewers to BitBucketServer import to 16-2](gitlab-org/security/gitlab@aeb33292029aae649352dea089d9e86933e01a80)
|
||||
|
||||
### Fixed (2 changes)
|
||||
|
||||
- [Disable IAT verification by default](gitlab-org/security/gitlab@b3a0c02721101596c644443d412ca13e8f4ce000)
|
||||
- [Enable descendant_security_scans by default](gitlab-org/security/gitlab@66eaaabed118b3b4b75fca17ef13e56b64e4eb4b) **GitLab Enterprise Edition**
|
||||
|
||||
### Security (17 changes)
|
||||
|
||||
- [Fix undefined method licenses for nil:NilClass bug](gitlab-org/security/gitlab@aa4c4dc26a239e7799f9e9aa14d893c7a696d112) ([merge request](gitlab-org/security/gitlab!3471))
|
||||
- [Fix undefined method page error in list dependencies](gitlab-org/security/gitlab@08acd6aa91d34de2403e1d2a28b437c58af107c1) ([merge request](gitlab-org/security/gitlab!3470))
|
||||
- [Add pagination for license scanning](gitlab-org/security/gitlab@b58ed3a7c40dd08ab0fe48c0cc4386e1cb7fa48a) ([merge request](gitlab-org/security/gitlab!3467))
|
||||
- [Prevent leaking emails of newly created users](gitlab-org/security/gitlab@25d75bb2494dffb7e2b55f3b9d190a7302461fe1) ([merge request](gitlab-org/security/gitlab!3449))
|
||||
- [Added redirect to filtered params](gitlab-org/security/gitlab@a72a1d48e871716ebeae3a4082078d4626cab8a0) ([merge request](gitlab-org/security/gitlab!3441))
|
||||
- [Relocate PlantUML config and disable SVG support](gitlab-org/security/gitlab@6aac3a3e7223cbb85a62d6e95cf096e8a582cfcf) ([merge request](gitlab-org/security/gitlab!3438))
|
||||
- [Sanitize multiple hardlinks from import archives](gitlab-org/security/gitlab@286c5b4e79f1a94554ee20b2535376d7c1c329a8) ([merge request](gitlab-org/security/gitlab!3435))
|
||||
- [Validates project path availability](gitlab-org/security/gitlab@d970c230a0dc1113de469e4636bac020fa7cdeac) ([merge request](gitlab-org/security/gitlab!3426))
|
||||
- [Fix policy project assign](gitlab-org/security/gitlab@5564547ac37f5f80c58f444778bdaf3e3a491ff7) ([merge request](gitlab-org/security/gitlab!3423))
|
||||
- [Fix bug where comments on files with incorrect sha breaks UI](gitlab-org/security/gitlab@da3ff8cc07ec7449f8bf1092ad1219fc2debbe53) ([merge request](gitlab-org/security/gitlab!3446))
|
||||
- [Fix pipeline schedule authorization for protected branch/tag](gitlab-org/security/gitlab@0d9a0ab46faa06cc33144096382f1375f95e91bd) ([merge request](gitlab-org/security/gitlab!3413))
|
||||
- [Mitigate autolink filter ReDOS](gitlab-org/security/gitlab@f8d9f5fa2617f1e372c6e477bc3840fd2161d7a7) ([merge request](gitlab-org/security/gitlab!3434))
|
||||
- [Fix XSS vector in Web IDE](gitlab-org/security/gitlab@530b97903782b2108284d47e33421e21f576ec8e) ([merge request](gitlab-org/security/gitlab!3409))
|
||||
- [Mitigate project reference filter ReDOS](gitlab-org/security/gitlab@93f7983d73955244994a131f7093ab8699347eb6) ([merge request](gitlab-org/security/gitlab!3431))
|
||||
- [Add a stricter regex for the Harbor search param](gitlab-org/security/gitlab@988280e72ac7570825e7451c528b718d38be681c) ([merge request](gitlab-org/security/gitlab!3408))
|
||||
- [Update pipeline user to the last policy MR author](gitlab-org/security/gitlab@85f3797d29aa957df88b4f8eaf47ec102021dd1a) ([merge request](gitlab-org/security/gitlab!3421))
|
||||
- [Prohibit 40 character hex plus a hyphen if branch name is path](gitlab-org/security/gitlab@14fabf71a196dcfa6af566084f0b5266be4bfe2d) ([merge request](gitlab-org/security/gitlab!3405))
|
||||
|
||||
## 16.2.1 (2023-07-25)
|
||||
|
||||
### Fixed (1 change)
|
||||
|
|
@ -725,6 +756,34 @@ entry.
|
|||
- [Add schema_version in the commits index mapping](gitlab-org/gitlab@e75b94903b69e1e1588e251217926882875555a8) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123435)) **GitLab Enterprise Edition**
|
||||
- [Allow to set labels for Redis calls](gitlab-org/gitlab@8ccfff9e2d250eb22afaa7d0243e707b536a5436) ([merge request](gitlab-org/gitlab!122340))
|
||||
|
||||
## 16.1.3 (2023-08-01)
|
||||
|
||||
### Added (1 change)
|
||||
|
||||
- [Add MR reviewers to BitBucketServer import 16-1](gitlab-org/security/gitlab@809b12fa28898efaccece784646092473e9dfb5c)
|
||||
|
||||
### Fixed (2 changes)
|
||||
|
||||
- [Disable IAT verification by default](gitlab-org/security/gitlab@a39f14f42e1ae503b3ade9333e2bbac7dff514a8)
|
||||
- [Fix FOUC when new sidebar enabled](gitlab-org/security/gitlab@0bf00a542eb6d83b074ce730dbd843913a1fd202)
|
||||
|
||||
### Security (14 changes)
|
||||
|
||||
- [Prevent leaking emails of newly created users](gitlab-org/security/gitlab@09a942563ef87dc1f2173564459c2663d69ed890) ([merge request](gitlab-org/security/gitlab!3450))
|
||||
- [Added redirect to filtered params](gitlab-org/security/gitlab@b3c74cec27cba2f65193d95cd6d4cf574e879e73) ([merge request](gitlab-org/security/gitlab!3442))
|
||||
- [Relocate PlantUML config and disable SVG support](gitlab-org/security/gitlab@dc66dcbb79a59b9f9669aeb72b7bb0df945f9b5e) ([merge request](gitlab-org/security/gitlab!3439))
|
||||
- [Sanitize multiple hardlinks from import archives](gitlab-org/security/gitlab@b6fd6a45d0f7352c7c5834b0948256168d41fdf1) ([merge request](gitlab-org/security/gitlab!3436))
|
||||
- [Validates project path availability](gitlab-org/security/gitlab@33a10c4089b2fb582295378d294914eb35dd6e8f) ([merge request](gitlab-org/security/gitlab!3427))
|
||||
- [Fix policy project assign](gitlab-org/security/gitlab@a24fefa04d87ad60c042b3df0e47ad0f1cd52ce2) ([merge request](gitlab-org/security/gitlab!3424))
|
||||
- [Fix bug where comments on files with incorrect sha breaks UI](gitlab-org/security/gitlab@eae7051f11f84ed2cc952253aff1d9fe0c1d5ff2) ([merge request](gitlab-org/security/gitlab!3447))
|
||||
- [Fix pipeline schedule authorization for protected branch/tag](gitlab-org/security/gitlab@2445a07db2cd5a135da26f09e1abfd047c38f4c8) ([merge request](gitlab-org/security/gitlab!3364))
|
||||
- [Mitigate autolink filter ReDOS](gitlab-org/security/gitlab@db8358c81b7c801d43bd84edca11d244c8bcdd69) ([merge request](gitlab-org/security/gitlab!3433))
|
||||
- [Fix XSS vector in Web IDE](gitlab-org/security/gitlab@77fde66185001cb1cf510eb17d027f37c969d21e) ([merge request](gitlab-org/security/gitlab!3410))
|
||||
- [Mitigate project reference filter ReDOS](gitlab-org/security/gitlab@fafb649e957c37c065420dbb8e577fd4ed24b6c9) ([merge request](gitlab-org/security/gitlab!3430))
|
||||
- [Add a stricter regex for the Harbor search param](gitlab-org/security/gitlab@0cf36d19bfd32d691ae1e60276ee3ac24c626c6b) ([merge request](gitlab-org/security/gitlab!3395))
|
||||
- [Update pipeline user to the last policy MR author](gitlab-org/security/gitlab@8b55561d397f848cb879903b47e47bded7af0a75) ([merge request](gitlab-org/security/gitlab!3392))
|
||||
- [Prohibit 40 character hex plus a hyphen if branch name is path](gitlab-org/security/gitlab@f2bcf18740a4398eebe2a1373b578bbe9f533f44) ([merge request](gitlab-org/security/gitlab!3407))
|
||||
|
||||
## 16.1.2 (2023-07-04)
|
||||
|
||||
### Fixed (4 changes)
|
||||
|
|
@ -1683,6 +1742,28 @@ entry.
|
|||
- [Migrate custom CSS to utility classes](gitlab-org/gitlab@a67999317bec111d523c763fc865665d4ded0aaf) ([merge request](gitlab-org/gitlab!120745)) **GitLab Enterprise Edition**
|
||||
- [Remove the vsa_group_and_project_parity FF](gitlab-org/gitlab@d090818bdedb0e220928d8e456cf36c8bce81f42) ([merge request](gitlab-org/gitlab!120727)) **GitLab Enterprise Edition**
|
||||
|
||||
## 16.0.8 (2023-08-01)
|
||||
|
||||
### Fixed (1 change)
|
||||
|
||||
- [Disable IAT verification by default](gitlab-org/security/gitlab@6d17a50539b8518da18bc68accc03b48d73173a0)
|
||||
|
||||
### Security (13 changes)
|
||||
|
||||
- [Prevent leaking emails of newly created users](gitlab-org/security/gitlab@b2872b398599cd7ee20c4119ae4c8e6ba2a6882d) ([merge request](gitlab-org/security/gitlab!3451))
|
||||
- [Added redirect to filtered params](gitlab-org/security/gitlab@49ffc2cc98af0e66305c8a653c74e0b92ee06ce8) ([merge request](gitlab-org/security/gitlab!3443))
|
||||
- [Relocate PlantUML config and disable SVG support](gitlab-org/security/gitlab@c6ded17a7d17ec8c3ed55cb94b8e6e524b6bbd5e) ([merge request](gitlab-org/security/gitlab!3440))
|
||||
- [Sanitize multiple hardlinks from import archives](gitlab-org/security/gitlab@9dabd8ebca50d8ea3781a0c4955a40cd07c453e7) ([merge request](gitlab-org/security/gitlab!3437))
|
||||
- [Validates project path availability](gitlab-org/security/gitlab@97e6ce4d15c8f4bcc7f60a560b789a023d391531) ([merge request](gitlab-org/security/gitlab!3428))
|
||||
- [Fix policy project assign](gitlab-org/security/gitlab@c1cca8ce8f24f6466563a50463e3254c5c423e97) ([merge request](gitlab-org/security/gitlab!3425))
|
||||
- [Fix pipeline schedule authorization for protected branch/tag](gitlab-org/security/gitlab@0c7017d993a33ef9fc693d4435505a4aea0141d1) ([merge request](gitlab-org/security/gitlab!3363))
|
||||
- [Mitigate autolink filter ReDOS](gitlab-org/security/gitlab@9072c630608a81645548b64b32d9f81bd258ba06) ([merge request](gitlab-org/security/gitlab!3432))
|
||||
- [Fix XSS vector in Web IDE](gitlab-org/security/gitlab@2832d1ae3b3e1bfc42bbeaeb29841a1e5fecac8a) ([merge request](gitlab-org/security/gitlab!3411))
|
||||
- [Mitigate project reference filter ReDOS](gitlab-org/security/gitlab@9c73619acaad3eb3605bf632f066bcee59b86566) ([merge request](gitlab-org/security/gitlab!3429))
|
||||
- [Add a stricter regex for the Harbor search param](gitlab-org/security/gitlab@c27e5e48a02d3411e84617b4fb7fd3f0fb49b618) ([merge request](gitlab-org/security/gitlab!3396))
|
||||
- [Update pipeline user to the last policy MR author](gitlab-org/security/gitlab@b1e9bcb33106ba7e279d5fd42c4f2c1727629f63) ([merge request](gitlab-org/security/gitlab!3393))
|
||||
- [Prohibit 40 character hex plus a hyphen if branch name is path](gitlab-org/security/gitlab@66c81ff6b50d0e53fc1f1b153439ad95614c9d09) ([merge request](gitlab-org/security/gitlab!3406))
|
||||
|
||||
## 16.0.7 (2023-07-04)
|
||||
|
||||
### Security (1 change)
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ export default {
|
|||
originalStartLineCode,
|
||||
...(discussion.line_codes || []),
|
||||
];
|
||||
const fileHash = discussion.diff_file.file_hash;
|
||||
const fileHash = discussion.diff_file?.file_hash;
|
||||
const lineCheck = (line) =>
|
||||
discussionLineCodes.some(
|
||||
(discussionLineCode) =>
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ export function prepareRawDiffFile({ file, allFiles, meta = false, index = -1 })
|
|||
}
|
||||
|
||||
export function collapsedType(file) {
|
||||
const isManual = typeof file.viewer?.manuallyCollapsed === 'boolean';
|
||||
const isManual = typeof file?.viewer?.manuallyCollapsed === 'boolean';
|
||||
|
||||
return isManual ? DIFF_FILE_MANUAL_COLLAPSE : DIFF_FILE_AUTOMATIC_COLLAPSE;
|
||||
}
|
||||
|
|
@ -85,8 +85,8 @@ export function collapsedType(file) {
|
|||
export function isCollapsed(file) {
|
||||
const type = collapsedType(file);
|
||||
const collapsedStates = {
|
||||
[DIFF_FILE_AUTOMATIC_COLLAPSE]: file.viewer?.automaticallyCollapsed || false,
|
||||
[DIFF_FILE_MANUAL_COLLAPSE]: file.viewer?.manuallyCollapsed,
|
||||
[DIFF_FILE_AUTOMATIC_COLLAPSE]: file?.viewer?.automaticallyCollapsed || false,
|
||||
[DIFF_FILE_MANUAL_COLLAPSE]: file?.viewer?.manuallyCollapsed,
|
||||
};
|
||||
|
||||
return collapsedStates[type];
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export default {
|
|||
return getDiffMode(this.discussion.diff_file);
|
||||
},
|
||||
diffViewerMode() {
|
||||
return this.discussion.diff_file.viewer.name;
|
||||
return this.discussion.diff_file?.viewer.name;
|
||||
},
|
||||
fileDiffRefs() {
|
||||
return this.discussion.diff_file.diff_refs;
|
||||
|
|
@ -96,6 +96,7 @@ export default {
|
|||
<template>
|
||||
<div :class="{ 'text-file': isTextFile }" class="diff-file file-holder">
|
||||
<diff-file-header
|
||||
v-if="discussion.diff_file"
|
||||
:discussion-path="discussion.discussion_path"
|
||||
:diff-file="discussion.diff_file"
|
||||
:can-current-user-fork="false"
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ export default {
|
|||
return !this.discussionResolved ? this.discussion.resolve_with_issue_path : '';
|
||||
},
|
||||
canShowReplyActions() {
|
||||
if (this.shouldRenderDiffs && !this.discussion.diff_file.diff_refs) {
|
||||
if (this.shouldRenderDiffs && !this.discussion.diff_file?.diff_refs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def new
|
||||
@schedule = project.pipeline_schedules.new
|
||||
end
|
||||
|
||||
def create
|
||||
|
|
@ -101,6 +100,15 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
|
|||
variables_attributes: [:id, :variable_type, :key, :secret_value, :_destroy])
|
||||
end
|
||||
|
||||
def new_schedule
|
||||
# We need the `ref` here for `authorize_create_pipeline_schedule!`
|
||||
@schedule ||= project.pipeline_schedules.new(ref: params.dig(:schedule, :ref))
|
||||
end
|
||||
|
||||
def authorize_create_pipeline_schedule!
|
||||
return access_denied! unless can?(current_user, :create_pipeline_schedule, new_schedule)
|
||||
end
|
||||
|
||||
def authorize_play_pipeline_schedule!
|
||||
return access_denied! unless can?(current_user, :play_pipeline_schedule, schedule)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WorkItems
|
||||
class NamespaceWorkItemsFinder < WorkItemsFinder
|
||||
def execute
|
||||
items = init_collection
|
||||
|
||||
sort(items)
|
||||
end
|
||||
|
||||
override :with_confidentiality_access_check
|
||||
def with_confidentiality_access_check
|
||||
return klass.none unless parent && current_user&.can?("read_#{parent.to_ability_name}".to_sym, parent)
|
||||
return model_class.all if params.user_can_see_all_issuables?
|
||||
|
||||
# Only admins can see hidden issues, so for non-admins, we filter out any hidden issues
|
||||
issues = model_class.without_hidden.in_namespaces(parent)
|
||||
|
||||
return issues.all if params.user_can_see_all_confidential_issues?
|
||||
|
||||
return issues.public_only if params.user_cannot_see_confidential_issues?
|
||||
|
||||
issues.with_confidentiality_check(current_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WorkItems
|
||||
module LookAheadPreloads
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
prepended do
|
||||
include ::LooksAhead
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def preloads
|
||||
{
|
||||
work_item_type: :work_item_type,
|
||||
web_url: { namespace: :route, project: [:project_namespace, { namespace: :route }] },
|
||||
widgets: { work_item_type: :enabled_widget_definitions }
|
||||
}
|
||||
end
|
||||
|
||||
def nested_preloads
|
||||
{
|
||||
widgets: widget_preloads,
|
||||
user_permissions: { update_work_item: :assignees },
|
||||
project: { jira_import_status: { project: :jira_imports } },
|
||||
author: {
|
||||
location: { author: :user_detail },
|
||||
gitpod_enabled: { author: :user_preference }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def widget_preloads
|
||||
{
|
||||
last_edited_by: :last_edited_by,
|
||||
assignees: :assignees,
|
||||
parent: :work_item_parent,
|
||||
children: { work_item_children_by_relative_position: [:author, { project: :project_feature }] },
|
||||
labels: :labels,
|
||||
milestone: { milestone: [:project, :group] },
|
||||
subscribed: [:assignees, :award_emoji, { notes: [:author, :award_emoji] }],
|
||||
award_emoji: { award_emoji: :awardable }
|
||||
}
|
||||
end
|
||||
|
||||
def unconditional_includes
|
||||
[
|
||||
{
|
||||
project: [:project_feature, :group]
|
||||
},
|
||||
:author
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
WorkItems::LookAheadPreloads.prepend_mod
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Resolvers
|
||||
module Namespaces
|
||||
class WorkItemsResolver < BaseResolver
|
||||
prepend ::WorkItems::LookAheadPreloads
|
||||
|
||||
type Types::WorkItemType.connection_type, null: true
|
||||
|
||||
def resolve_with_lookahead(**args)
|
||||
return unless Feature.enabled?(:namespace_level_work_items)
|
||||
return WorkItem.none if resource_parent.nil?
|
||||
|
||||
finder = ::WorkItems::NamespaceWorkItemsFinder.new(current_user, args)
|
||||
|
||||
Gitlab::Graphql::Loaders::IssuableLoader.new(resource_parent, finder).batching_find_all do |q|
|
||||
apply_lookahead(q)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resource_parent
|
||||
# The project could have been loaded in batch by `BatchLoader`.
|
||||
# At this point we need the `id` of the project to query for work items, so
|
||||
# make sure it's loaded and not `nil` before continuing.
|
||||
object.respond_to?(:sync) ? object.sync : object
|
||||
end
|
||||
strong_memoize_attr :resource_parent
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
module Resolvers
|
||||
class WorkItemsResolver < BaseResolver
|
||||
prepend ::WorkItems::LookAheadPreloads
|
||||
include SearchArguments
|
||||
include LooksAhead
|
||||
include ::WorkItems::SharedFilterArguments
|
||||
|
||||
argument :iid,
|
||||
|
|
@ -28,48 +28,6 @@ module Resolvers
|
|||
|
||||
private
|
||||
|
||||
def preloads
|
||||
{
|
||||
work_item_type: :work_item_type,
|
||||
web_url: { namespace: :route, project: [:project_namespace, { namespace: :route }] },
|
||||
widgets: { work_item_type: :enabled_widget_definitions }
|
||||
}
|
||||
end
|
||||
|
||||
def nested_preloads
|
||||
{
|
||||
widgets: widget_preloads,
|
||||
user_permissions: { update_work_item: :assignees },
|
||||
project: { jira_import_status: { project: :jira_imports } },
|
||||
author: {
|
||||
location: { author: :user_detail },
|
||||
gitpod_enabled: { author: :user_preference }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def widget_preloads
|
||||
{
|
||||
last_edited_by: :last_edited_by,
|
||||
assignees: :assignees,
|
||||
parent: :work_item_parent,
|
||||
children: { work_item_children_by_relative_position: [:author, { project: :project_feature }] },
|
||||
labels: :labels,
|
||||
milestone: { milestone: [:project, :group] },
|
||||
subscribed: [:assignees, :award_emoji, { notes: [:author, :award_emoji] }],
|
||||
award_emoji: { award_emoji: :awardable }
|
||||
}
|
||||
end
|
||||
|
||||
def unconditional_includes
|
||||
[
|
||||
{
|
||||
project: [:project_feature, :group]
|
||||
},
|
||||
:author
|
||||
]
|
||||
end
|
||||
|
||||
def prepare_finder_params(args)
|
||||
params = super(args)
|
||||
params[:iids] ||= [params.delete(:iid)].compact if params[:iid]
|
||||
|
|
@ -88,4 +46,4 @@ module Resolvers
|
|||
end
|
||||
end
|
||||
|
||||
Resolvers::WorkItemsResolver.prepend_mod_with('Resolvers::WorkItemsResolver')
|
||||
Resolvers::WorkItemsResolver.prepend_mod
|
||||
|
|
|
|||
|
|
@ -262,6 +262,12 @@ module Types
|
|||
resolver: Resolvers::DataTransfer::GroupDataTransferResolver,
|
||||
description: 'Data transfer data point for a specific period. This is mocked data under a development feature flag.'
|
||||
|
||||
field :work_items,
|
||||
null: true,
|
||||
description: 'Work items that belong to the namespace.',
|
||||
alpha: { milestone: '16.3' },
|
||||
resolver: ::Resolvers::Namespaces::WorkItemsResolver
|
||||
|
||||
def label(title:)
|
||||
BatchLoader::GraphQL.for(title).batch(key: group) do |titles, loader, args|
|
||||
LabelsFinder
|
||||
|
|
|
|||
|
|
@ -52,7 +52,9 @@ module Milestoneable
|
|||
def milestone_available?
|
||||
return true if milestone_id.blank?
|
||||
|
||||
project_id == milestone&.project_id || project.ancestors_upto.compact.include?(milestone&.group)
|
||||
(project_id.present? && project_id == milestone&.project_id) ||
|
||||
try(:namespace)&.self_and_ancestors&.include?(milestone&.group) ||
|
||||
project&.ancestors_upto&.compact&.include?(milestone&.group)
|
||||
end
|
||||
|
||||
##
|
||||
|
|
|
|||
|
|
@ -585,6 +585,8 @@ class Project < ApplicationRecord
|
|||
validates :max_artifacts_size, numericality: { only_integer: true, greater_than: 0, allow_nil: true }
|
||||
validates :suggestion_commit_message, length: { maximum: MAX_SUGGESTIONS_TEMPLATE_LENGTH }
|
||||
|
||||
validate :path_availability, if: :path_changed?
|
||||
|
||||
# Scopes
|
||||
scope :pending_delete, -> { where(pending_delete: true) }
|
||||
scope :without_deleted, -> { where(pending_delete: false) }
|
||||
|
|
@ -3230,6 +3232,15 @@ class Project < ApplicationRecord
|
|||
group.crm_enabled?
|
||||
end
|
||||
|
||||
def path_availability
|
||||
base, _, host = path.partition('.')
|
||||
|
||||
return unless host == Gitlab.config.pages&.dig('host')
|
||||
return unless ProjectSetting.where(pages_unique_domain: base).exists?
|
||||
|
||||
errors.add(:path, s_('Project|already in use'))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# overridden in EE
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ class ProjectSetting < ApplicationRecord
|
|||
|
||||
validate :validates_mr_default_target_self
|
||||
|
||||
validate :pages_unique_domain_availability, if: :pages_unique_domain_changed?
|
||||
|
||||
attribute :legacy_open_source_license_available, default: -> do
|
||||
Feature.enabled?(:legacy_open_source_license_available, type: :ops)
|
||||
end
|
||||
|
|
@ -114,6 +116,15 @@ class ProjectSetting < ApplicationRecord
|
|||
pages_unique_domain_enabled ||
|
||||
pages_unique_domain_in_database.present?
|
||||
end
|
||||
|
||||
def pages_unique_domain_availability
|
||||
host = Gitlab.config.pages&.dig('host')
|
||||
|
||||
return if host.blank?
|
||||
return unless Project.where(path: "#{pages_unique_domain}.#{host}").exists?
|
||||
|
||||
errors.add(:pages_unique_domain, s_('ProjectSetting|already in use'))
|
||||
end
|
||||
end
|
||||
|
||||
ProjectSetting.prepend_mod
|
||||
|
|
|
|||
|
|
@ -22,6 +22,18 @@ class WorkItem < Issue
|
|||
foreign_key: :work_item_id, source: :work_item
|
||||
|
||||
scope :inc_relations_for_permission_check, -> { includes(:author, project: :project_feature) }
|
||||
scope :in_namespaces, ->(namespaces) { where(namespace: namespaces) }
|
||||
|
||||
scope :with_confidentiality_check, ->(user) {
|
||||
confidential_query = <<~SQL
|
||||
issues.confidential = FALSE
|
||||
OR (issues.confidential = TRUE
|
||||
AND (issues.author_id = :user_id
|
||||
OR EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = :user_id AND issue_id = issues.id)))
|
||||
SQL
|
||||
|
||||
where(confidential_query, user_id: user.id)
|
||||
}
|
||||
|
||||
class << self
|
||||
def assignee_association_name
|
||||
|
|
|
|||
|
|
@ -5,7 +5,18 @@ module Ci
|
|||
alias_method :pipeline_schedule, :subject
|
||||
|
||||
condition(:protected_ref) do
|
||||
ref_protected?(@user, @subject.project, @subject.project.repository.tag_exists?(@subject.ref), @subject.ref)
|
||||
if full_ref?(@subject.ref)
|
||||
is_tag = Gitlab::Git.tag_ref?(@subject.ref)
|
||||
ref_name = Gitlab::Git.ref_name(@subject.ref)
|
||||
else
|
||||
# NOTE: this block should not be removed
|
||||
# until the full ref validation is in place
|
||||
# and all old refs are updated and validated
|
||||
is_tag = @subject.project.repository.tag_exists?(@subject.ref)
|
||||
ref_name = @subject.ref
|
||||
end
|
||||
|
||||
ref_protected?(@user, @subject.project, is_tag, ref_name)
|
||||
end
|
||||
|
||||
condition(:owner_of_schedule) do
|
||||
|
|
@ -31,6 +42,15 @@ module Ci
|
|||
enable :take_ownership_pipeline_schedule
|
||||
end
|
||||
|
||||
rule { protected_ref }.prevent :play_pipeline_schedule
|
||||
rule { protected_ref }.policy do
|
||||
prevent :play_pipeline_schedule
|
||||
prevent :create_pipeline_schedule
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def full_ref?(ref)
|
||||
Gitlab::Git.tag_ref?(ref) || Gitlab::Git.branch_ref?(ref)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -148,6 +148,7 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
|
|||
enable :read_group_member
|
||||
enable :read_custom_emoji
|
||||
enable :read_counts
|
||||
enable :read_issue
|
||||
end
|
||||
|
||||
rule { achievements_enabled }.policy do
|
||||
|
|
|
|||
|
|
@ -49,11 +49,7 @@ module BulkImports
|
|||
end
|
||||
|
||||
def validate_symlink
|
||||
raise(BulkImports::Error, 'Invalid file') if symlink?(filepath)
|
||||
end
|
||||
|
||||
def symlink?(filepath)
|
||||
File.lstat(filepath).symlink?
|
||||
raise(BulkImports::Error, 'Invalid file') if Gitlab::Utils::FileInfo.linked?(filepath)
|
||||
end
|
||||
|
||||
def extract_archive
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ module BulkImports
|
|||
end
|
||||
|
||||
def validate_symlink(filepath)
|
||||
raise(ServiceError, 'Invalid file') if File.lstat(filepath).symlink?
|
||||
raise(ServiceError, 'Invalid file') if Gitlab::Utils::FileInfo.linked?(filepath)
|
||||
end
|
||||
|
||||
def decompress_file
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ module Discussions
|
|||
active_diff_discussions = merge_request.notes.new_diff_notes.discussions.select do |discussion|
|
||||
discussion.active?(merge_request.diff_refs)
|
||||
end
|
||||
paths = active_diff_discussions.flat_map { |n| n.diff_file.paths }
|
||||
paths = active_diff_discussions.flat_map { |n| n.diff_file&.paths }
|
||||
|
||||
[active_diff_discussions, paths]
|
||||
end
|
||||
|
|
|
|||
|
|
@ -186,6 +186,7 @@ module Gitlab
|
|||
# - Any parameter containing `password`
|
||||
# - Any parameter containing `secret`
|
||||
# - Any parameter ending with `key`
|
||||
# - Any parameter named `redirect`, filtered for security concerns of exposing sensitive information
|
||||
# - Two-factor tokens (:otp_attempt)
|
||||
# - Repo/Project Import URLs (:import_url)
|
||||
# - Build traces (:trace)
|
||||
|
|
@ -228,6 +229,7 @@ module Gitlab
|
|||
variables
|
||||
content
|
||||
sharedSecret
|
||||
redirect
|
||||
)
|
||||
|
||||
# This config option can be removed after Rails 7.1 by https://gitlab.com/gitlab-org/gitlab/-/issues/416270
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: use_primary_and_secondary_stores_for_etag_cache
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127705
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/419889
|
||||
milestone: '16.3'
|
||||
type: development
|
||||
group: group::scalability
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: use_primary_store_as_default_for_etag_cache
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127705
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/419889
|
||||
milestone: '16.3'
|
||||
type: development
|
||||
group: group::scalability
|
||||
default_enabled: false
|
||||
|
|
@ -136,7 +136,7 @@ To use instance-level or group-level default settings for a project integration:
|
|||
1. Select **Settings > Integrations**.
|
||||
1. Select an integration.
|
||||
1. On the right, from the dropdown list, select **Use default settings**.
|
||||
1. In **Enable integration**, ensure the **Active** checkbox is selected.
|
||||
1. Under **Enable integration**, ensure the **Active** checkbox is selected.
|
||||
1. Complete the fields.
|
||||
1. Select **Save changes**.
|
||||
|
||||
|
|
@ -148,6 +148,6 @@ To use custom settings for a project or group integration:
|
|||
1. Select **Settings > Integrations**.
|
||||
1. Select an integration.
|
||||
1. On the right, from the dropdown list, select **Use custom settings**.
|
||||
1. In **Enable integration**, ensure the **Active** checkbox is selected.
|
||||
1. Under **Enable integration**, ensure the **Active** checkbox is selected.
|
||||
1. Complete the fields.
|
||||
1. Select **Save changes**.
|
||||
|
|
|
|||
|
|
@ -16462,6 +16462,7 @@ GPG signature for a signed commit.
|
|||
| <a id="groupvisibility"></a>`visibility` | [`String`](#string) | Visibility of the namespace. |
|
||||
| <a id="groupvulnerabilityscanners"></a>`vulnerabilityScanners` | [`VulnerabilityScannerConnection`](#vulnerabilityscannerconnection) | Vulnerability scanners reported on the project vulnerabilities of the group and its subgroups. (see [Connections](#connections)) |
|
||||
| <a id="groupweburl"></a>`webUrl` | [`String!`](#string) | Web URL of the group. |
|
||||
| <a id="groupworkitems"></a>`workItems` **{warning-solid}** | [`WorkItemConnection`](#workitemconnection) | **Introduced** in 16.3. This feature is an Experiment. It can be changed or removed at any time. Work items that belong to the namespace. |
|
||||
|
||||
#### Fields with arguments
|
||||
|
||||
|
|
|
|||
|
|
@ -299,7 +299,7 @@ to build and release Android apps. To enable the integration:
|
|||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **Google Play**.
|
||||
1. In **Enable integration**, select the **Active** checkbox.
|
||||
1. Under **Enable integration**, select the **Active** checkbox.
|
||||
1. In **Package name**, enter the package name of the app. For example, `com.gitlab.app_name`.
|
||||
1. In **Service account key (.JSON)** drag or upload your key file.
|
||||
1. Select **Save changes**.
|
||||
|
|
@ -356,7 +356,7 @@ to build and release apps for iOS, iPadOS, macOS, tvOS, and watchOS. To enable t
|
|||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **Apple App Store**.
|
||||
1. Turn on the **Active** toggle under **Enable Integration**.
|
||||
1. Under **Enable integration**, select the **Active** checkbox.
|
||||
1. Provide the Apple App Store Connect configuration information:
|
||||
- **Issuer ID**: You can find the Apple App Store Connect Issuer ID in the **Keys** section under **Users and Access** in the Apple App Store Connect portal.
|
||||
- **Key ID**: The key ID of the new private key that was just generated.
|
||||
|
|
|
|||
|
|
@ -123,6 +123,14 @@ index 5fa7ae8a2bc1..5fe996ba0345 100644
|
|||
def valid?
|
||||
```
|
||||
|
||||
### Working with GitLab Duo Chat
|
||||
|
||||
Prompts are the most vital part of GitLab Duo Chat system. Prompts are the instructions sent to the Large Language Model to perform certain tasks.
|
||||
|
||||
The state of the prompts is the result of weeks of iteration. If you want to change any prompt in the current tool, you must put it behind a feature flag.
|
||||
|
||||
If you have any new or updated prompts, ask members of AI Framework team to review, because they have significant experience with them.
|
||||
|
||||
### Setup for GitLab documentation chat (legacy chat)
|
||||
|
||||
To populate the embedding database for GitLab chat:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ To configure your project settings in GitLab:
|
|||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **Jira**.
|
||||
1. In **Enable integration**, select the **Active** checkbox.
|
||||
1. Under **Enable integration**, select the **Active** checkbox.
|
||||
1. Provide connection details:
|
||||
- **Web URL**: Base URL for the Jira instance web interface you're linking to
|
||||
this GitLab project (for example, `https://jira.example.com`).
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ To remove the Harbor Registry for a project:
|
|||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project or group.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **Harbor** under **Active integrations**.
|
||||
1. Clear the **Active** checkbox under **Enable integration**.
|
||||
1. Under **Enable integration**, clear the **Active** checkbox.
|
||||
1. Select **Save changes**.
|
||||
|
||||
The **Operate > Harbor Registry** entry is removed from the sidebar.
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ GitLab supports enabling the Apple App Store integration at the project level. C
|
|||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **Apple App Store Connect**.
|
||||
1. Turn on the **Active** toggle under **Enable Integration**.
|
||||
1. Under **Enable integration**, select the **Active** checkbox.
|
||||
1. Provide the Apple App Store Connect configuration information:
|
||||
- **Issuer ID**: The Apple App Store Connect issuer ID.
|
||||
- **Key ID**: The key ID of the generated private key.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ To enable the Bugzilla integration in a project:
|
|||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **Bugzilla**.
|
||||
1. Select the checkbox under **Enable integration**.
|
||||
1. Under **Enable integration**, select the **Active** checkbox.
|
||||
1. Fill in the required fields:
|
||||
|
||||
- **Project URL**: The URL to the project in Bugzilla.
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ To enable the ClickUp integration in a project:
|
|||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **ClickUp**.
|
||||
1. Select the checkbox under **Enable integration**.
|
||||
1. Under **Enable integration**, select the **Active** checkbox.
|
||||
1. Fill in the required fields:
|
||||
|
||||
- **Project URL**: The URL to the ClickUp project to link to this GitLab project.
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ To enable a custom issue tracker in a project:
|
|||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **Custom issue tracker**.
|
||||
1. Select the checkbox under **Enable integration**.
|
||||
1. Under **Enable integration**, select the **Active** checkbox.
|
||||
1. Fill in the required fields:
|
||||
|
||||
- **Project URL**: The URL to view all the issues in the custom issue tracker.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ To enable the EWM integration, in a project:
|
|||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **EWM**.
|
||||
1. Select the checkbox under **Enable integration**.
|
||||
1. Under **Enable integration**, select the **Active** checkbox.
|
||||
1. Fill in the required fields:
|
||||
|
||||
- **Project URL**: The URL to the EWM project area.
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ To enable the Google Play integration in GitLab:
|
|||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **Google Play**.
|
||||
1. In **Enable integration**, select the **Active** checkbox.
|
||||
1. Under **Enable integration**, select the **Active** checkbox.
|
||||
1. In **Package name**, enter the package name of the app (for example, `com.gitlab.app_name`).
|
||||
1. In **Service account key (.JSON)**, drag or upload your key file.
|
||||
1. Select **Save changes**.
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ GitLab supports integrating Harbor projects at the group or project level. Compl
|
|||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **Harbor**.
|
||||
1. Turn on the **Active** toggle under **Enable Integration**.
|
||||
1. Under **Enable integration**, select the **Active** checkbox.
|
||||
1. Provide the Harbor configuration information:
|
||||
- **Harbor URL**: The base URL of Harbor instance which is being linked to this GitLab project. For example, `https://harbor.example.net`.
|
||||
- **Harbor project name**: The project name in the Harbor instance. For example, `testproject`.
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ you can automatically configure Mattermost slash commands:
|
|||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **Mattermost slash commands**.
|
||||
1. In **Enable integration**, ensure the **Active** checkbox is selected.
|
||||
1. Under **Enable integration**, ensure the **Active** checkbox is selected.
|
||||
1. Select **Add to Mattermost**, and select **Save changes**.
|
||||
|
||||
## Configure manually
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ To enable the Redmine integration in a project:
|
|||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **Redmine**.
|
||||
1. Select the checkbox under **Enable integration**.
|
||||
1. Under **Enable integration**, select the **Active** checkbox.
|
||||
1. Fill in the required fields:
|
||||
|
||||
- **Project URL**: The URL to the Redmine project to link to this GitLab project.
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ To enable the Shimo integration for your project:
|
|||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **Shimo**.
|
||||
1. In **Enable integration**, ensure the **Active** checkbox is selected.
|
||||
1. Under **Enable integration**, ensure the **Active** checkbox is selected.
|
||||
1. Provide the **Shimo Workspace URL** you want to link to your group or project (for example, `https://shimo.im/space/aBAYV6VvajUP873j`).
|
||||
1. Select **Save changes**.
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ to control GitLab from Slack. Slash commands are configured separately.
|
|||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **Slack notifications**.
|
||||
1. In the **Enable integration** section, select the **Active** checkbox.
|
||||
1. Under **Enable integration**, select the **Active** checkbox.
|
||||
1. In the **Trigger** section, select the checkboxes for each type of GitLab
|
||||
event to send to Slack as a notification. For a full list, see
|
||||
[Triggers for Slack notifications](#triggers-for-slack-notifications).
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ After you invite the bot to a Telegram channel, you can configure GitLab to send
|
|||
1. Select **Admin Area**.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **Telegram**.
|
||||
1. In **Enable integration**, select the **Active** checkbox.
|
||||
1. Under **Enable integration**, select the **Active** checkbox.
|
||||
1. In **New token**, [paste the token value from the Telegram bot](#create-a-telegram-bot).
|
||||
1. In the **Trigger** section, select the checkboxes for the GitLab events you want to receive in Telegram.
|
||||
1. In **Channel identifier**, [paste the Telegram channel identifier](#configure-the-telegram-bot).
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ To enable the YouTrack integration in a project:
|
|||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **YouTrack**.
|
||||
1. Select the checkbox under **Enable integration**.
|
||||
1. Under **Enable integration**, select the **Active** checkbox.
|
||||
1. Fill in the required fields:
|
||||
- **Project URL**: The URL to the project in YouTrack.
|
||||
- **Issue URL**: The URL to view an issue in the YouTrack project.
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ Complete these steps in GitLab:
|
|||
|
||||
1. Go to your project and select **Settings > Integrations**.
|
||||
1. Select **ZenTao**.
|
||||
1. Turn on the **Active** toggle under **Enable Integration**.
|
||||
1. Under **Enable integration**, select the **Active** checkbox.
|
||||
1. Provide the ZenTao configuration information:
|
||||
- **ZenTao Web URL**: The base URL of the ZenTao instance web interface you're linking to this GitLab project (for example, `example.zentao.net`).
|
||||
- **ZenTao API URL** (optional): The base URL to the ZenTao instance API. Defaults to the Web URL value if not set.
|
||||
|
|
|
|||
|
|
@ -287,7 +287,7 @@ To hide the link to an external wiki:
|
|||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Select **External wiki**.
|
||||
1. In the **Enable integration** section, clear the **Active** checkbox.
|
||||
1. Under **Enable integration**, clear the **Active** checkbox.
|
||||
1. Select **Save changes**.
|
||||
|
||||
## Disable the project's wiki
|
||||
|
|
|
|||
|
|
@ -65,6 +65,10 @@ Graphql/AuthorizeTypes:
|
|||
Graphql/Descriptions:
|
||||
Enabled: false
|
||||
|
||||
# This cop doesn't make sense in the context of gems
|
||||
RSpec/BeforeAll:
|
||||
Enabled: false
|
||||
|
||||
Naming/FileName:
|
||||
Exclude:
|
||||
- spec/**/*.rb
|
||||
|
|
|
|||
|
|
@ -8,7 +8,3 @@ RSpec/InstanceVariable:
|
|||
Gitlab/ChangeTimezone:
|
||||
Exclude:
|
||||
- spec/gitlab/rspec/time_travel_spec.rb
|
||||
|
||||
RSpec/BeforeAll:
|
||||
Exclude:
|
||||
- spec/gitlab/rspec/time_travel_spec.rb
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe 'time travel' do
|
||||
# rubocop:disable RSpec/BeforeAll
|
||||
before(:all) do
|
||||
@original_time_zone = Time.zone
|
||||
Time.zone = 'Eastern Time (US & Canada)'
|
||||
|
|
@ -10,7 +9,6 @@ RSpec.describe 'time travel' do
|
|||
after(:all) do
|
||||
Time.zone = @original_time_zone
|
||||
end
|
||||
# rubocop:enable RSpec/BeforeAll
|
||||
|
||||
describe ':freeze_time' do
|
||||
it 'freezes time around a spec example', :freeze_time do
|
||||
|
|
|
|||
|
|
@ -34,8 +34,13 @@ module Banzai
|
|||
# https://github.com/vmg/rinku/blob/v2.0.1/ext/rinku/autolink.c#L65
|
||||
#
|
||||
# Rubular: http://rubular.com/r/nrL3r9yUiq
|
||||
# Note that it's not possible to use Gitlab::UntrustedRegexp for LINK_PATTERN,
|
||||
# as `(?<!` is unsupported in `re2`, see https://github.com/google/re2/wiki/Syntax
|
||||
LINK_PATTERN = %r{([a-z][a-z0-9\+\.-]+://[^\s>]+)(?<!\?|!|\.|,|:)}.freeze
|
||||
|
||||
ENTITY_UNTRUSTED = '((?:&[\w#]+;)+)\z'
|
||||
ENTITY_UNTRUSTED_REGEX = Gitlab::UntrustedRegexp.new(ENTITY_UNTRUSTED, multiline: false)
|
||||
|
||||
# Text matching LINK_PATTERN inside these elements will not be linked
|
||||
IGNORE_PARENTS = %w(a code kbd pre script style).to_set
|
||||
|
||||
|
|
@ -85,10 +90,14 @@ module Banzai
|
|||
# Remove any trailing HTML entities and store them for appending
|
||||
# outside the link element. The entity must be marked HTML safe in
|
||||
# order to be output literally rather than escaped.
|
||||
match.gsub!(/((?:&[\w#]+;)+)\z/, '')
|
||||
dropped = (Regexp.last_match(1) || '').html_safe
|
||||
dropped = ''
|
||||
match = ENTITY_UNTRUSTED_REGEX.replace_gsub(match) do |entities|
|
||||
dropped = entities[1].html_safe
|
||||
|
||||
# To match the behaviour of Rinku, if the matched link ends with a
|
||||
''
|
||||
end
|
||||
|
||||
# To match the behavior of Rinku, if the matched link ends with a
|
||||
# closing part of a matched pair of punctuation, we remove that trailing
|
||||
# character unless there are an equal number of closing and opening
|
||||
# characters in the link.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ module Banzai
|
|||
def call
|
||||
return doc unless settings.plantuml_enabled? && doc.at_xpath(lang_tag)
|
||||
|
||||
plantuml_setup
|
||||
Gitlab::Plantuml.configure
|
||||
|
||||
doc.xpath(lang_tag).each do |node|
|
||||
img_tag = Nokogiri::HTML::DocumentFragment.parse(
|
||||
|
|
@ -38,15 +38,6 @@ module Banzai
|
|||
def settings
|
||||
Gitlab::CurrentSettings.current_application_settings
|
||||
end
|
||||
|
||||
def plantuml_setup
|
||||
Asciidoctor::PlantUml.configure do |conf|
|
||||
conf.url = settings.plantuml_url
|
||||
conf.png_enable = settings.plantuml_enabled
|
||||
conf.svg_enable = false
|
||||
conf.txt_enable = false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ module BulkImports
|
|||
return if tar_filepath?(file_path)
|
||||
return if lfs_json_filepath?(file_path)
|
||||
return if File.directory?(file_path)
|
||||
return if File.lstat(file_path).symlink?
|
||||
return if Gitlab::Utils::FileInfo.linked?(file_path)
|
||||
|
||||
size = File.size(file_path)
|
||||
oid = LfsObject.calculate_oid(file_path)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ module BulkImports
|
|||
# Validate that the path is OK to load
|
||||
Gitlab::PathTraversal.check_allowed_absolute_path_and_path_traversal!(file_path, [Dir.tmpdir])
|
||||
return if File.directory?(file_path)
|
||||
return if File.lstat(file_path).symlink?
|
||||
return if Gitlab::Utils::FileInfo.linked?(file_path)
|
||||
|
||||
avatar_path = AVATAR_PATTERN.match(file_path)
|
||||
return save_avatar(file_path) if avatar_path
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ module BulkImports
|
|||
end
|
||||
|
||||
def validate_symlink
|
||||
return unless File.lstat(filepath).symlink?
|
||||
return unless Gitlab::Utils::FileInfo.linked?(filepath)
|
||||
|
||||
File.delete(filepath)
|
||||
raise_error 'Invalid downloaded file'
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ module BulkImports
|
|||
return unless portable.lfs_enabled?
|
||||
return unless File.exist?(bundle_path)
|
||||
return if File.directory?(bundle_path)
|
||||
return if File.lstat(bundle_path).symlink?
|
||||
return if Gitlab::Utils::FileInfo.linked?(bundle_path)
|
||||
|
||||
portable.design_repository.create_from_bundle(bundle_path)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ module BulkImports
|
|||
|
||||
return unless File.exist?(bundle_path)
|
||||
return if File.directory?(bundle_path)
|
||||
return if File.lstat(bundle_path).symlink?
|
||||
return if Gitlab::Utils::FileInfo.linked?(bundle_path)
|
||||
|
||||
portable.repository.create_from_bundle(bundle_path)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -78,20 +78,11 @@ module Gitlab
|
|||
context[:pipeline] = :ascii_doc
|
||||
context[:max_includes] = [MAX_INCLUDES, context[:max_includes]].compact.min
|
||||
|
||||
plantuml_setup
|
||||
Gitlab::Plantuml.configure
|
||||
|
||||
html = ::Asciidoctor.convert(input, asciidoc_opts)
|
||||
html = Banzai.render(html, context)
|
||||
html.html_safe
|
||||
end
|
||||
|
||||
def self.plantuml_setup
|
||||
Asciidoctor::PlantUml.configure do |conf|
|
||||
conf.url = Gitlab::CurrentSettings.plantuml_url
|
||||
conf.svg_enable = Gitlab::CurrentSettings.plantuml_enabled
|
||||
conf.png_enable = Gitlab::CurrentSettings.plantuml_enabled
|
||||
conf.txt_enable = false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ module Gitlab
|
|||
def prohibited_branch_checks
|
||||
return if deletion?
|
||||
|
||||
if %r{\A#{Gitlab::Git::Commit::RAW_FULL_SHA_PATTERN}(/|\z)}o.match?(branch_name)
|
||||
if %r{\A#{Gitlab::Git::Commit::RAW_FULL_SHA_PATTERN}(-/|/|\z)}o.match?(branch_name)
|
||||
raise GitAccess::ForbiddenError, ERROR_MESSAGES[:prohibited_hex_branch_name]
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ module Gitlab
|
|||
def validate_archive_path
|
||||
Gitlab::PathTraversal.check_path_traversal!(archive_path)
|
||||
|
||||
raise(ServiceError, 'Archive path is a symlink') if File.lstat(archive_path).symlink?
|
||||
raise(ServiceError, 'Archive path is a symlink or hard link') if Gitlab::Utils::FileInfo.linked?(archive_path)
|
||||
raise(ServiceError, 'Archive path is not a file') unless File.file?(archive_path)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -9,15 +9,15 @@ module Gitlab
|
|||
SHARED_STATE_NAMESPACE = 'etag:'
|
||||
|
||||
def get(key)
|
||||
Gitlab::Redis::SharedState.with { |redis| redis.get(redis_shared_state_key(key)) }
|
||||
with_redis { |redis| redis.get(redis_shared_state_key(key)) }
|
||||
end
|
||||
|
||||
def touch(*keys, only_if_missing: false)
|
||||
etags = keys.map { generate_etag }
|
||||
|
||||
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.pipelined do |pipeline|
|
||||
with_redis do |redis|
|
||||
Gitlab::Redis::CrossSlot::Pipeline.new(redis).pipelined do |pipeline|
|
||||
keys.each_with_index do |key, i|
|
||||
pipeline.set(redis_shared_state_key(key), etags[i], ex: EXPIRY_TIME, nx: only_if_missing)
|
||||
end
|
||||
|
|
@ -30,6 +30,12 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
def with_redis(&blk)
|
||||
# We use multistore as n interweaving double-write will result in n-1 subsequent requests
|
||||
# becoming a cache-miss, however, 2 interweaving .touch will lead to 1 cache miss anyway.
|
||||
Gitlab::Redis::EtagCache.with(&blk) # rubocop:disable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
||||
def generate_etag
|
||||
SecureRandom.hex
|
||||
end
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ module Gitlab
|
|||
message: 'params invalid'
|
||||
}, allow_blank: true
|
||||
validates :search, format: {
|
||||
with: /\A([a-z\_]*=[a-zA-Z0-9\- :]*,*)*\z/,
|
||||
with: /\A(name=[a-zA-Z0-9\-:]+(?:,name=[a-zA-Z0-9\-:]+)*)\z/,
|
||||
message: 'params invalid'
|
||||
}, allow_blank: true
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@ module Gitlab
|
|||
module CommandLineUtil
|
||||
UNTAR_MASK = 'u+rwX,go+rX,go-w'
|
||||
DEFAULT_DIR_MODE = 0700
|
||||
CLEAN_DIR_IGNORE_FILE_NAMES = %w[. ..].freeze
|
||||
|
||||
FileOversizedError = Class.new(StandardError)
|
||||
CommandLineUtilError = Class.new(StandardError)
|
||||
FileOversizedError = Class.new(CommandLineUtilError)
|
||||
HardLinkError = Class.new(CommandLineUtilError)
|
||||
|
||||
def tar_czf(archive:, dir:)
|
||||
tar_with_options(archive: archive, dir: dir, options: 'czf')
|
||||
|
|
@ -90,7 +93,7 @@ module Gitlab
|
|||
def untar_with_options(archive:, dir:, options:)
|
||||
execute_cmd(%W(tar -#{options} #{archive} -C #{dir}))
|
||||
execute_cmd(%W(chmod -R #{UNTAR_MASK} #{dir}))
|
||||
remove_symlinks(dir)
|
||||
clean_extraction_dir!(dir)
|
||||
end
|
||||
|
||||
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
|
|
@ -122,17 +125,27 @@ module Gitlab
|
|||
true
|
||||
end
|
||||
|
||||
def remove_symlinks(dir)
|
||||
ignore_file_names = %w[. ..]
|
||||
|
||||
# Scans and cleans the directory tree.
|
||||
# Symlinks are considered legal but are removed.
|
||||
# Files sharing hard links are considered illegal and the directory will be removed
|
||||
# and a `HardLinkError` exception will be raised.
|
||||
#
|
||||
# @raise [HardLinkError] if there multiple hard links to the same file detected.
|
||||
# @return [Boolean] true
|
||||
def clean_extraction_dir!(dir)
|
||||
# Using File::FNM_DOTMATCH to also delete symlinks starting with "."
|
||||
Dir.glob("#{dir}/**/*", File::FNM_DOTMATCH)
|
||||
.reject { |f| ignore_file_names.include?(File.basename(f)) }
|
||||
.each do |filepath|
|
||||
FileUtils.rm(filepath) if File.lstat(filepath).symlink?
|
||||
end
|
||||
Dir.glob("#{dir}/**/*", File::FNM_DOTMATCH).each do |filepath|
|
||||
next if CLEAN_DIR_IGNORE_FILE_NAMES.include?(File.basename(filepath))
|
||||
|
||||
raise HardLinkError, 'File shares hard link' if Gitlab::Utils::FileInfo.shares_hard_link?(filepath)
|
||||
|
||||
FileUtils.rm(filepath) if Gitlab::Utils::FileInfo.linked?(filepath)
|
||||
end
|
||||
|
||||
true
|
||||
rescue HardLinkError
|
||||
FileUtils.remove_dir(dir)
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ module Gitlab
|
|||
def validate_archive_path
|
||||
Gitlab::PathTraversal.check_path_traversal!(@archive_path)
|
||||
|
||||
raise(ServiceError, 'Archive path is a symlink') if File.lstat(@archive_path).symlink?
|
||||
raise(ServiceError, 'Archive path is a symlink or hard link') if Gitlab::Utils::FileInfo.linked?(@archive_path)
|
||||
raise(ServiceError, 'Archive path is not a file') unless File.file?(@archive_path)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ module Gitlab
|
|||
mkdir_p(@shared.export_path)
|
||||
mkdir_p(@shared.archive_path)
|
||||
|
||||
remove_symlinks(@shared.export_path)
|
||||
clean_extraction_dir!(@shared.export_path)
|
||||
copy_archive
|
||||
|
||||
wait_for_archived_file do
|
||||
|
|
@ -35,7 +35,7 @@ module Gitlab
|
|||
false
|
||||
ensure
|
||||
remove_import_file
|
||||
remove_symlinks(@shared.export_path)
|
||||
clean_extraction_dir!(@shared.export_path)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ module Gitlab
|
|||
# This reads from `tree/project.json`
|
||||
path = file_path("#{importable_path}.json")
|
||||
|
||||
raise Gitlab::ImportExport::Error, 'Invalid file' if !File.exist?(path) || File.symlink?(path)
|
||||
if !File.exist?(path) || Gitlab::Utils::FileInfo.linked?(path)
|
||||
raise Gitlab::ImportExport::Error, 'Invalid file'
|
||||
end
|
||||
|
||||
data = File.read(path, MAX_JSON_DOCUMENT_SIZE)
|
||||
json_decode(data)
|
||||
|
|
@ -34,7 +36,7 @@ module Gitlab
|
|||
# This reads from `tree/project/merge_requests.ndjson`
|
||||
path = file_path(importable_path, "#{key}.ndjson")
|
||||
|
||||
next if !File.exist?(path) || File.symlink?(path)
|
||||
next if !File.exist?(path) || Gitlab::Utils::FileInfo.linked?(path)
|
||||
|
||||
File.foreach(path, MAX_JSON_DOCUMENT_SIZE).with_index do |line, line_num|
|
||||
documents << [json_decode(line), line_num]
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ module Gitlab
|
|||
source_child = File.join(source_path, child)
|
||||
target_child = File.join(target_path, child)
|
||||
|
||||
next if File.lstat(source_child).symlink?
|
||||
next if Gitlab::Utils::FileInfo.linked?(source_child)
|
||||
|
||||
if File.directory?(source_child)
|
||||
FileUtils.mkdir_p(target_child, mode: DEFAULT_DIR_MODE) unless File.exist?(target_child)
|
||||
|
|
|
|||
|
|
@ -10,13 +10,12 @@ module Gitlab
|
|||
def execute
|
||||
return if host.blank?
|
||||
|
||||
gitlab_host = ::Settings.pages.host.downcase.prepend(".")
|
||||
gitlab_host = ::Gitlab.config.pages.host.downcase.prepend(".")
|
||||
|
||||
if host.ends_with?(gitlab_host)
|
||||
name = host.delete_suffix(gitlab_host)
|
||||
|
||||
by_namespace_domain(name) ||
|
||||
by_unique_domain(name)
|
||||
by_unique_domain(name) || by_namespace_domain(name)
|
||||
else
|
||||
by_custom_domain(host)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ module Gitlab
|
|||
# `NAMESPACE_FORMAT_REGEX`, with the negative lookbehind assertion removed. This means that the client-side validation
|
||||
# will pass for usernames ending in `.atom` and `.git`, but will be caught by the server-side validation.
|
||||
PATH_START_CHAR = '[a-zA-Z0-9_\.]'
|
||||
PATH_REGEX_STR = PATH_START_CHAR + '[a-zA-Z0-9_\-\.]*'
|
||||
PATH_REGEX_STR = PATH_START_CHAR + '[a-zA-Z0-9_\-\.]' + "{0,#{Namespace::URL_MAX_LENGTH - 1}}"
|
||||
NAMESPACE_FORMAT_REGEX_JS = PATH_REGEX_STR + '[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'
|
||||
|
||||
NO_SUFFIX_REGEX = /(?<!\.git|\.atom)/.freeze
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "asciidoctor_plantuml/plantuml"
|
||||
|
||||
module Gitlab
|
||||
module Plantuml
|
||||
class << self
|
||||
def configure
|
||||
Asciidoctor::PlantUml.configure do |conf|
|
||||
conf.url = Gitlab::CurrentSettings.plantuml_url
|
||||
conf.png_enable = Gitlab::CurrentSettings.plantuml_enabled
|
||||
conf.svg_enable = false
|
||||
conf.txt_enable = false
|
||||
|
||||
conf
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Redis
|
||||
class EtagCache < ::Gitlab::Redis::Wrapper
|
||||
class << self
|
||||
def store_name
|
||||
'Cache'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def redis
|
||||
primary_store = ::Redis.new(Gitlab::Redis::Cache.params)
|
||||
secondary_store = ::Redis.new(Gitlab::Redis::SharedState.params)
|
||||
|
||||
MultiStore.new(primary_store, secondary_store, name.demodulize)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Utils
|
||||
module FileInfo
|
||||
class << self
|
||||
# Returns true if:
|
||||
# - File or directory is a symlink.
|
||||
# - File shares a hard link.
|
||||
def linked?(file)
|
||||
stat = to_file_stat(file)
|
||||
|
||||
stat.symlink? || shares_hard_link?(stat)
|
||||
end
|
||||
|
||||
# Returns:
|
||||
# - true if file shares a hard link with another file.
|
||||
# - false if file is a directory, as directories cannot be hard linked.
|
||||
def shares_hard_link?(file)
|
||||
stat = to_file_stat(file)
|
||||
|
||||
stat.file? && stat.nlink > 1
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def to_file_stat(filepath_or_stat)
|
||||
return filepath_or_stat if filepath_or_stat.is_a?(File::Stat)
|
||||
|
||||
File.lstat(filepath_or_stat)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Sbom
|
||||
module PurlType
|
||||
class Converter
|
||||
PACKAGE_MANAGER_TO_PURL_TYPE_MAP = {
|
||||
'bundler' => 'gem',
|
||||
'yarn' => 'npm',
|
||||
'npm' => 'npm',
|
||||
'pnpm' => 'npm',
|
||||
'maven' => 'maven',
|
||||
'sbt' => 'maven',
|
||||
'gradle' => 'maven',
|
||||
'composer' => 'composer',
|
||||
'conan' => 'conan',
|
||||
'go' => 'golang',
|
||||
'nuget' => 'nuget',
|
||||
'pip' => 'pypi',
|
||||
'pipenv' => 'pypi',
|
||||
'setuptools' => 'pypi'
|
||||
}.with_indifferent_access.freeze
|
||||
|
||||
def self.purl_type_for_pkg_manager(package_manager)
|
||||
PACKAGE_MANAGER_TO_PURL_TYPE_MAP[package_manager]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -36943,6 +36943,9 @@ msgstr ""
|
|||
msgid "ProjectSettings|Your project is set up. %{linkStart}View instrumentation instructions%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSetting|already in use"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectTemplates|.NET Core"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -37240,6 +37243,9 @@ msgstr ""
|
|||
msgid "ProjectsNew|Your project will be created at:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Project|already in use"
|
||||
msgstr ""
|
||||
|
||||
msgid "PrometheusAlerts|exceeded"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -55008,9 +55014,6 @@ msgstr ""
|
|||
msgid "eligible users"
|
||||
msgstr ""
|
||||
|
||||
msgid "email '%{email}' is not a verified email."
|
||||
msgstr ""
|
||||
|
||||
msgid "email address settings"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -55301,6 +55304,9 @@ msgstr ""
|
|||
msgid "is not valid. The iteration group has to match the iteration cadence group."
|
||||
msgstr ""
|
||||
|
||||
msgid "is not verified."
|
||||
msgstr ""
|
||||
|
||||
msgid "is one of"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"@gitlab/svgs": "3.58.0",
|
||||
"@gitlab/ui": "64.20.1",
|
||||
"@gitlab/visual-review-tools": "1.7.3",
|
||||
"@gitlab/web-ide": "0.0.1-dev-20230713160749",
|
||||
"@gitlab/web-ide": "0.0.1-dev-20230713160749-patch-1",
|
||||
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
|
||||
"@popperjs/core": "^2.11.2",
|
||||
"@rails/actioncable": "7.0.6",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe Projects::PipelineSchedulesController, feature_category: :continuous_integration do
|
||||
include AccessMatchersForController
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be_with_reload(:project) { create(:project, :public, :repository) }
|
||||
|
|
@ -45,6 +46,43 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples 'protecting ref' do
|
||||
where(:branch_access_levels, :tag_access_level, :maintainer_accessible, :developer_accessible) do
|
||||
[:no_one_can_push, :no_one_can_merge] | :no_one_can_create | \
|
||||
:be_denied_for | :be_denied_for
|
||||
[:maintainers_can_push, :maintainers_can_merge] | :maintainers_can_create | \
|
||||
:be_allowed_for | :be_denied_for
|
||||
[:developers_can_push, :developers_can_merge] | :developers_can_create | \
|
||||
:be_allowed_for | :be_allowed_for
|
||||
end
|
||||
|
||||
with_them do
|
||||
context 'when branch is protected' do
|
||||
let(:ref_prefix) { 'heads' }
|
||||
let(:ref_name) { 'master' }
|
||||
|
||||
before do
|
||||
create(:protected_branch, *branch_access_levels, name: ref_name, project: project)
|
||||
end
|
||||
|
||||
it { expect { go }.to try(maintainer_accessible, :maintainer).of(project) }
|
||||
it { expect { go }.to try(developer_accessible, :developer).of(project) }
|
||||
end
|
||||
|
||||
context 'when tag is protected' do
|
||||
let(:ref_prefix) { 'tags' }
|
||||
let(:ref_name) { 'v1.0.0' }
|
||||
|
||||
before do
|
||||
create(:protected_tag, tag_access_level, name: ref_name, project: project)
|
||||
end
|
||||
|
||||
it { expect { go }.to try(maintainer_accessible, :maintainer).of(project) }
|
||||
it { expect { go }.to try(developer_accessible, :developer).of(project) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #index' do
|
||||
render_views
|
||||
|
||||
|
|
@ -172,7 +210,9 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
|
|||
end
|
||||
|
||||
describe 'security' do
|
||||
let(:schedule) { attributes_for(:ci_pipeline_schedule) }
|
||||
let(:schedule) { attributes_for(:ci_pipeline_schedule, ref: "refs/#{ref_prefix}/#{ref_name}") }
|
||||
let(:ref_prefix) { 'heads' }
|
||||
let(:ref_name) { "master" }
|
||||
|
||||
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
|
||||
expect { go }.to be_allowed_for(:admin)
|
||||
|
|
@ -191,6 +231,8 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
|
|||
it { expect { go }.to be_denied_for(:user) }
|
||||
it { expect { go }.to be_denied_for(:external) }
|
||||
it { expect { go }.to be_denied_for(:visitor) }
|
||||
|
||||
it_behaves_like 'protecting ref'
|
||||
end
|
||||
|
||||
def go
|
||||
|
|
@ -457,7 +499,7 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
|
|||
end
|
||||
|
||||
describe 'POST #play', :clean_gitlab_redis_rate_limiting do
|
||||
let(:ref) { 'master' }
|
||||
let(:ref_name) { 'master' }
|
||||
|
||||
before do
|
||||
project.add_developer(user)
|
||||
|
|
@ -473,7 +515,7 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
|
|||
it 'does not allow pipeline to be executed' do
|
||||
expect(RunPipelineScheduleWorker).not_to receive(:perform_async)
|
||||
|
||||
post :play, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id }
|
||||
go
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
|
|
@ -483,16 +525,14 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
|
|||
it 'executes a new pipeline' do
|
||||
expect(RunPipelineScheduleWorker).to receive(:perform_async).with(pipeline_schedule.id, user.id).and_return('job-123')
|
||||
|
||||
post :play, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id }
|
||||
go
|
||||
|
||||
expect(flash[:notice]).to start_with 'Successfully scheduled a pipeline to run'
|
||||
expect(response).to have_gitlab_http_status(:found)
|
||||
end
|
||||
|
||||
it 'prevents users from scheduling the same pipeline repeatedly' do
|
||||
2.times do
|
||||
post :play, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id }
|
||||
end
|
||||
2.times { go }
|
||||
|
||||
expect(flash.to_a.size).to eq(2)
|
||||
expect(flash[:alert]).to eq _('You cannot play this scheduled pipeline at the moment. Please wait a minute.')
|
||||
|
|
@ -500,17 +540,14 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
|
|||
end
|
||||
end
|
||||
|
||||
context 'when a developer attempts to schedule a protected ref' do
|
||||
it 'does not allow pipeline to be executed' do
|
||||
create(:protected_branch, project: project, name: ref)
|
||||
protected_schedule = create(:ci_pipeline_schedule, project: project, ref: ref)
|
||||
describe 'security' do
|
||||
let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, ref: "refs/#{ref_prefix}/#{ref_name}") }
|
||||
|
||||
expect(RunPipelineScheduleWorker).not_to receive(:perform_async)
|
||||
it_behaves_like 'protecting ref'
|
||||
end
|
||||
|
||||
post :play, params: { namespace_id: project.namespace.to_param, project_id: project, id: protected_schedule.id }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
def go
|
||||
post :play, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,10 @@ FactoryBot.define do
|
|||
end
|
||||
end
|
||||
|
||||
factory :file_diff_position do
|
||||
position_type { 'file' }
|
||||
end
|
||||
|
||||
factory :image_diff_position do
|
||||
position_type { 'image' }
|
||||
x { 1 }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'User views comment on a diff file', :js, feature_category: :code_review_workflow do
|
||||
include MergeRequestDiffHelpers
|
||||
include RepoHelpers
|
||||
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
let_it_be(:merge_request) do
|
||||
create(:merge_request_with_diffs, source_project: project, target_project: project, source_branch: 'merge-test')
|
||||
end
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
before_all do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
|
||||
visit(diffs_project_merge_request_path(project, merge_request))
|
||||
end
|
||||
|
||||
context 'with invalid start_sha position' do
|
||||
before do
|
||||
diff_refs = Gitlab::Diff::DiffRefs.new(
|
||||
base_sha: merge_request.diff_refs.base_sha,
|
||||
start_sha: 'fakesha',
|
||||
head_sha: merge_request.diff_refs.head_sha
|
||||
)
|
||||
position = build(:file_diff_position, file: 'files/ruby/popen.rb', diff_refs: diff_refs)
|
||||
create(:diff_note_on_merge_request, noteable: merge_request, project: project, position: position)
|
||||
end
|
||||
|
||||
it 'renders diffs' do
|
||||
visit diffs_project_merge_request_path(project, merge_request)
|
||||
|
||||
expect(page).to have_selector('.diff-file')
|
||||
end
|
||||
|
||||
it 'renders discussion on overview tab' do
|
||||
visit project_merge_request_path(project, merge_request)
|
||||
|
||||
expect(page).to have_selector('.note-discussion')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe WorkItems::NamespaceWorkItemsFinder, feature_category: :team_planning do
|
||||
include AdminModeHelper
|
||||
|
||||
describe '#execute' do
|
||||
let_it_be(:group) { create(:group, :public) }
|
||||
let_it_be(:sub_group) { create(:group, :private, parent: group) }
|
||||
let_it_be(:project) { create(:project, :repository, :public, group: group) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:reporter) { create(:user).tap { |user| group.add_reporter(user) } }
|
||||
let_it_be(:guest) { create(:user).tap { |user| group.add_guest(user) } }
|
||||
let_it_be(:guest_author) { create(:user).tap { |user| group.add_guest(user) } }
|
||||
let_it_be(:banned_user) { create(:banned_user) }
|
||||
|
||||
let_it_be(:project_work_item) { create(:work_item, project: project) }
|
||||
let_it_be(:sub_group_work_item) do
|
||||
create(:work_item, namespace: sub_group, author: reporter)
|
||||
end
|
||||
|
||||
let_it_be(:group_work_item) do
|
||||
create(:work_item, namespace: group, author: reporter)
|
||||
end
|
||||
|
||||
let_it_be(:group_confidential_work_item, reload: true) do
|
||||
create(:work_item, :confidential, namespace: group, author: guest_author)
|
||||
end
|
||||
|
||||
let_it_be(:sub_group_confidential_work_item, reload: true) do
|
||||
create(:work_item, :confidential, namespace: sub_group, author: guest_author)
|
||||
end
|
||||
|
||||
let_it_be(:hidden_work_item) do
|
||||
create(:work_item, :confidential, namespace: group, author: banned_user.user)
|
||||
end
|
||||
|
||||
let_it_be(:other_work_item) { create(:work_item) }
|
||||
let(:finder_params) { {} }
|
||||
let(:current_user) { user }
|
||||
let(:namespace) { nil }
|
||||
|
||||
subject do
|
||||
finder = described_class.new(current_user, finder_params)
|
||||
finder.parent_param = namespace
|
||||
|
||||
finder.execute
|
||||
end
|
||||
|
||||
context 'when no parent is provided' do
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
|
||||
context 'when the namespace is private' do
|
||||
let(:namespace) { sub_group }
|
||||
|
||||
context 'when the user cannot read the namespace' do
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
|
||||
context 'when the user can not see confidential work_items' do
|
||||
let(:current_user) { guest }
|
||||
|
||||
it { is_expected.to contain_exactly(sub_group_work_item) }
|
||||
|
||||
context 'when the user is the author of the work item' do
|
||||
let(:current_user) { guest_author }
|
||||
|
||||
it { is_expected.to contain_exactly(sub_group_work_item, sub_group_confidential_work_item) }
|
||||
end
|
||||
|
||||
context 'when the user is assigned to a confidential work item' do
|
||||
before do
|
||||
sub_group_confidential_work_item.update!(assignees: [current_user])
|
||||
end
|
||||
|
||||
it { is_expected.to contain_exactly(sub_group_work_item, sub_group_confidential_work_item) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user can see confidential work_items' do
|
||||
let(:current_user) { reporter }
|
||||
|
||||
it { is_expected.to contain_exactly(sub_group_work_item, sub_group_confidential_work_item) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the namespace is public' do
|
||||
let(:namespace) { group }
|
||||
|
||||
context 'when user is admin' do
|
||||
let(:current_user) { create(:user, :admin).tap { |u| enable_admin_mode!(u) } }
|
||||
|
||||
it { is_expected.to match_array(WorkItem.all.to_a) }
|
||||
end
|
||||
|
||||
context 'when the user can not see confidential work_items' do
|
||||
it { is_expected.to contain_exactly(group_work_item) }
|
||||
|
||||
context 'when the user is the author of the work item' do
|
||||
let(:current_user) { guest_author }
|
||||
|
||||
it { is_expected.to contain_exactly(group_work_item, group_confidential_work_item) }
|
||||
end
|
||||
|
||||
context 'when the user is assigned to a confidential work item' do
|
||||
before do
|
||||
group_confidential_work_item.update!(assignees: [current_user])
|
||||
end
|
||||
|
||||
it { is_expected.to contain_exactly(group_work_item, group_confidential_work_item) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user can see confidential work_items' do
|
||||
let(:current_user) { reporter }
|
||||
|
||||
it { is_expected.to contain_exactly(group_work_item, group_confidential_work_item) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -192,7 +192,7 @@ describe('InputCopyToggleVisibility', () => {
|
|||
describe('when input is clicked', () => {
|
||||
it('selects input value', async () => {
|
||||
const mockSelect = jest.fn();
|
||||
wrapper.vm.$refs.input.$el.select = mockSelect;
|
||||
findFormInput().element.select = mockSelect;
|
||||
await findFormInput().trigger('click');
|
||||
|
||||
expect(mockSelect).toHaveBeenCalled();
|
||||
|
|
@ -202,7 +202,7 @@ describe('InputCopyToggleVisibility', () => {
|
|||
describe('when label is clicked', () => {
|
||||
it('selects input value', async () => {
|
||||
const mockSelect = jest.fn();
|
||||
wrapper.vm.$refs.input.$el.select = mockSelect;
|
||||
findFormInput().element.select = mockSelect;
|
||||
await wrapper.find('label').trigger('click');
|
||||
|
||||
expect(mockSelect).toHaveBeenCalled();
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ RSpec.describe GitlabSchema.types['Group'] do
|
|||
dependency_proxy_image_prefix dependency_proxy_image_ttl_policy
|
||||
shared_runners_setting timelogs organization_state_counts organizations
|
||||
contact_state_counts contacts work_item_types
|
||||
recent_issue_boards ci_variables releases environment_scopes
|
||||
recent_issue_boards ci_variables releases environment_scopes work_items
|
||||
]
|
||||
|
||||
expect(described_class).to include_graphql_fields(*expected_fields)
|
||||
|
|
@ -77,6 +77,13 @@ RSpec.describe GitlabSchema.types['Group'] do
|
|||
it { is_expected.to have_graphql_resolver(Resolvers::GroupReleasesResolver) }
|
||||
end
|
||||
|
||||
describe 'work_items field' do
|
||||
subject { described_class.fields['workItems'] }
|
||||
|
||||
it { is_expected.to have_graphql_type(Types::WorkItemType.connection_type) }
|
||||
it { is_expected.to have_graphql_resolver(Resolvers::Namespaces::WorkItemsResolver) }
|
||||
end
|
||||
|
||||
it_behaves_like 'a GraphQL type with labels' do
|
||||
let(:labels_resolver_arguments) { [:search_term, :includeAncestorGroups, :includeDescendantGroups, :onlyGroupLabels] }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -226,6 +226,22 @@ RSpec.describe Banzai::Filter::AutolinkFilter, feature_category: :team_planning
|
|||
end
|
||||
end
|
||||
|
||||
it 'protects against malicious backtracking' do
|
||||
doc = "http://#{'&' * 1_000_000}x"
|
||||
|
||||
expect do
|
||||
Timeout.timeout(30.seconds) { filter(doc) }
|
||||
end.not_to raise_error
|
||||
end
|
||||
|
||||
it 'does not timeout with excessively long scheme' do
|
||||
doc = "#{'h' * 1_000_000}://example.com"
|
||||
|
||||
expect do
|
||||
Timeout.timeout(30.seconds) { filter(doc) }
|
||||
end.not_to raise_error
|
||||
end
|
||||
|
||||
# Rinku does not escape these characters in HTML attributes, but content_tag
|
||||
# does. We don't care about that difference for these specs, though.
|
||||
def unescape(html)
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ RSpec.describe Banzai::Filter::References::ProjectReferenceFilter, feature_categ
|
|||
|
||||
it_behaves_like 'fails fast', 'A' * 50000
|
||||
it_behaves_like 'fails fast', '/a' * 50000
|
||||
it_behaves_like 'fails fast', "mailto:#{'a-' * 499_000}@aaaaaaaa..aaaaaaaa.example.com"
|
||||
end
|
||||
|
||||
it 'allows references with text after the > character' do
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::Common::Pipelines::LfsObjectsPipeline do
|
||||
RSpec.describe BulkImports::Common::Pipelines::LfsObjectsPipeline, feature_category: :importers do
|
||||
let_it_be(:portable) { create(:project) }
|
||||
let_it_be(:oid) { 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' }
|
||||
|
||||
|
|
@ -118,13 +118,22 @@ RSpec.describe BulkImports::Common::Pipelines::LfsObjectsPipeline do
|
|||
context 'when file path is symlink' do
|
||||
it 'returns' do
|
||||
symlink = File.join(tmpdir, 'symlink')
|
||||
FileUtils.ln_s(lfs_file_path, symlink)
|
||||
|
||||
FileUtils.ln_s(File.join(tmpdir, lfs_file_path), symlink)
|
||||
|
||||
expect(Gitlab::Utils::FileInfo).to receive(:linked?).with(symlink).and_call_original
|
||||
expect { pipeline.load(context, symlink) }.not_to change { portable.lfs_objects.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when file path shares multiple hard links' do
|
||||
it 'returns' do
|
||||
FileUtils.link(lfs_file_path, File.join(tmpdir, 'hard_link'))
|
||||
|
||||
expect(Gitlab::Utils::FileInfo).to receive(:linked?).with(lfs_file_path).and_call_original
|
||||
expect { pipeline.load(context, lfs_file_path) }.not_to change { portable.lfs_objects.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when path is a directory' do
|
||||
it 'returns' do
|
||||
expect { pipeline.load(context, Dir.tmpdir) }.not_to change { portable.lfs_objects.count }
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ RSpec.describe BulkImports::Common::Pipelines::UploadsPipeline, feature_category
|
|||
it 'returns' do
|
||||
path = File.join(tmpdir, 'test')
|
||||
FileUtils.touch(path)
|
||||
|
||||
expect { pipeline.load(context, path) }.not_to change { portable.uploads.count }
|
||||
end
|
||||
end
|
||||
|
|
@ -118,13 +119,22 @@ RSpec.describe BulkImports::Common::Pipelines::UploadsPipeline, feature_category
|
|||
context 'when path is a symlink' do
|
||||
it 'does not upload the file' do
|
||||
symlink = File.join(tmpdir, 'symlink')
|
||||
FileUtils.ln_s(upload_file_path, symlink)
|
||||
|
||||
FileUtils.ln_s(File.join(tmpdir, upload_file_path), symlink)
|
||||
|
||||
expect(Gitlab::Utils::FileInfo).to receive(:linked?).with(symlink).and_call_original
|
||||
expect { pipeline.load(context, symlink) }.not_to change { portable.uploads.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when path has multiple hard links' do
|
||||
it 'does not upload the file' do
|
||||
FileUtils.link(upload_file_path, File.join(tmpdir, 'hard_link'))
|
||||
|
||||
expect(Gitlab::Utils::FileInfo).to receive(:linked?).with(upload_file_path).and_call_original
|
||||
expect { pipeline.load(context, upload_file_path) }.not_to change { portable.uploads.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when path traverses' do
|
||||
it 'does not upload the file' do
|
||||
path_traversal = "#{uploads_dir_path}/avatar/../../../../etc/passwd"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::Projects::Pipelines::DesignBundlePipeline do
|
||||
RSpec.describe BulkImports::Projects::Pipelines::DesignBundlePipeline, feature_category: :importers do
|
||||
let_it_be(:design) { create(:design, :with_file) }
|
||||
|
||||
let(:portable) { create(:project) }
|
||||
|
|
@ -125,9 +125,9 @@ RSpec.describe BulkImports::Projects::Pipelines::DesignBundlePipeline do
|
|||
context 'when path is symlink' do
|
||||
it 'returns' do
|
||||
symlink = File.join(tmpdir, 'symlink')
|
||||
FileUtils.ln_s(design_bundle_path, symlink)
|
||||
|
||||
FileUtils.ln_s(File.join(tmpdir, design_bundle_path), symlink)
|
||||
|
||||
expect(Gitlab::Utils::FileInfo).to receive(:linked?).with(symlink).and_call_original
|
||||
expect(portable.design_repository).not_to receive(:create_from_bundle)
|
||||
|
||||
pipeline.load(context, symlink)
|
||||
|
|
@ -136,6 +136,19 @@ RSpec.describe BulkImports::Projects::Pipelines::DesignBundlePipeline do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when path has multiple hard links' do
|
||||
it 'returns' do
|
||||
FileUtils.link(design_bundle_path, File.join(tmpdir, 'hard_link'))
|
||||
|
||||
expect(Gitlab::Utils::FileInfo).to receive(:linked?).with(design_bundle_path).and_call_original
|
||||
expect(portable.design_repository).not_to receive(:create_from_bundle)
|
||||
|
||||
pipeline.load(context, design_bundle_path)
|
||||
|
||||
expect(portable.design_repository.exists?).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when path is not under tmpdir' do
|
||||
it 'returns' do
|
||||
expect { pipeline.load(context, '/home/test.txt') }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::Projects::Pipelines::RepositoryBundlePipeline do
|
||||
RSpec.describe BulkImports::Projects::Pipelines::RepositoryBundlePipeline, feature_category: :importers do
|
||||
let_it_be(:source) { create(:project, :repository) }
|
||||
|
||||
let(:portable) { create(:project) }
|
||||
|
|
@ -123,9 +123,9 @@ RSpec.describe BulkImports::Projects::Pipelines::RepositoryBundlePipeline do
|
|||
context 'when path is symlink' do
|
||||
it 'returns' do
|
||||
symlink = File.join(tmpdir, 'symlink')
|
||||
FileUtils.ln_s(bundle_path, symlink)
|
||||
|
||||
FileUtils.ln_s(File.join(tmpdir, bundle_path), symlink)
|
||||
|
||||
expect(Gitlab::Utils::FileInfo).to receive(:linked?).with(symlink).and_call_original
|
||||
expect(portable.repository).not_to receive(:create_from_bundle)
|
||||
|
||||
pipeline.load(context, symlink)
|
||||
|
|
@ -134,6 +134,19 @@ RSpec.describe BulkImports::Projects::Pipelines::RepositoryBundlePipeline do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when path has mutiple hard links' do
|
||||
it 'returns' do
|
||||
FileUtils.link(bundle_path, File.join(tmpdir, 'hard_link'))
|
||||
|
||||
expect(Gitlab::Utils::FileInfo).to receive(:linked?).with(bundle_path).and_call_original
|
||||
expect(portable.repository).not_to receive(:create_from_bundle)
|
||||
|
||||
pipeline.load(context, bundle_path)
|
||||
|
||||
expect(portable.repository.exists?).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when path is not under tmpdir' do
|
||||
it 'returns' do
|
||||
expect { pipeline.load(context, '/home/test.txt') }
|
||||
|
|
|
|||
|
|
@ -32,6 +32,12 @@ RSpec.describe Gitlab::Checks::BranchCheck, feature_category: :source_code_manag
|
|||
expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a 40-character hexadecimal branch name.")
|
||||
end
|
||||
|
||||
it "prohibits 40-character hexadecimal branch names followed by a dash as the start of a path" do
|
||||
allow(subject).to receive(:branch_name).and_return("267208abfe40e546f5e847444276f7d43a39503e-/test")
|
||||
|
||||
expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a 40-character hexadecimal branch name.")
|
||||
end
|
||||
|
||||
it "prohibits 64-character hexadecimal branch names" do
|
||||
allow(subject).to receive(:branch_name).and_return("09b9fd3ea68e9b95a51b693a29568c898e27d1476bbd83c825664f18467fc175")
|
||||
|
||||
|
|
|
|||
|
|
@ -105,6 +105,16 @@ RSpec.describe Gitlab::Ci::DecompressedGzipSizeValidator, feature_category: :imp
|
|||
end
|
||||
end
|
||||
|
||||
context 'when archive path has multiple hard links' do
|
||||
before do
|
||||
FileUtils.link(filepath, File.join(Dir.mktmpdir, 'hard_link'))
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
expect(subject).not_to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context 'when archive path is not a file' do
|
||||
let(:filepath) { Dir.mktmpdir }
|
||||
let(:filesize) { File.size(filepath) }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::GithubImport::AttachmentsDownloader do
|
||||
RSpec.describe Gitlab::GithubImport::AttachmentsDownloader, feature_category: :importers do
|
||||
subject(:downloader) { described_class.new(file_url) }
|
||||
|
||||
let_it_be(:file_url) { 'https://example.com/avatar.png' }
|
||||
|
|
@ -39,6 +39,26 @@ RSpec.describe Gitlab::GithubImport::AttachmentsDownloader do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when file shares multiple hard links' do
|
||||
let(:tmpdir) { Dir.mktmpdir }
|
||||
let(:hard_link) { File.join(tmpdir, 'hard_link') }
|
||||
|
||||
before do
|
||||
existing_file = File.join(tmpdir, 'file.txt')
|
||||
FileUtils.touch(existing_file)
|
||||
FileUtils.link(existing_file, hard_link)
|
||||
allow(downloader).to receive(:filepath).and_return(hard_link)
|
||||
end
|
||||
|
||||
it 'raises expected exception' do
|
||||
expect(Gitlab::Utils::FileInfo).to receive(:linked?).with(hard_link).and_call_original
|
||||
expect { downloader.perform }.to raise_exception(
|
||||
described_class::DownloadError,
|
||||
'Invalid downloaded file'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when filename is malicious' do
|
||||
let_it_be(:file_url) { 'https://example.com/ava%2F..%2Ftar.png' }
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Harbor::Query do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let_it_be(:harbor_integration) { create(:harbor_integration) }
|
||||
|
||||
let(:params) { {} }
|
||||
|
|
@ -111,19 +113,20 @@ RSpec.describe Gitlab::Harbor::Query do
|
|||
end
|
||||
|
||||
context 'search' do
|
||||
context 'with valid search' do
|
||||
let(:params) { { search: 'name=desc' } }
|
||||
|
||||
it 'initialize successfully' do
|
||||
expect(query.valid?).to eq(true)
|
||||
end
|
||||
where(:search_param, :is_valid) do
|
||||
"name=desc" | true
|
||||
"name=value1,name=value-2" | true
|
||||
"name=value1,name=value_2" | false
|
||||
"name=desc,key=value" | false
|
||||
"name=value1, name=value2" | false
|
||||
"name" | false
|
||||
end
|
||||
|
||||
context 'with invalid search' do
|
||||
let(:params) { { search: 'blabla' } }
|
||||
with_them do
|
||||
let(:params) { { search: search_param } }
|
||||
|
||||
it 'initialize failed' do
|
||||
expect(query.valid?).to eq(false)
|
||||
it "validates according to the regex" do
|
||||
expect(query.valid?).to eq(is_valid)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,13 +5,16 @@ require 'spec_helper'
|
|||
RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importers do
|
||||
include ExportFileHelper
|
||||
|
||||
let(:path) { "#{Dir.tmpdir}/symlink_test" }
|
||||
let(:archive) { 'spec/fixtures/symlink_export.tar.gz' }
|
||||
let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
|
||||
let(:tmpdir) { Dir.mktmpdir }
|
||||
# Separate where files are written during this test by their kind, to avoid them interfering with each other:
|
||||
# - `source_dir` Dir to compress files from.
|
||||
# - `target_dir` Dir to decompress archived files into.
|
||||
# - `archive_dir` Dir to write any archive files to.
|
||||
let(:source_dir) { Dir.mktmpdir }
|
||||
let(:target_dir) { Dir.mktmpdir }
|
||||
let(:archive_dir) { Dir.mktmpdir }
|
||||
|
||||
subject do
|
||||
subject(:mock_class) do
|
||||
Class.new do
|
||||
include Gitlab::ImportExport::CommandLineUtil
|
||||
|
||||
|
|
@ -25,38 +28,59 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importe
|
|||
end
|
||||
|
||||
before do
|
||||
FileUtils.mkdir_p(path)
|
||||
FileUtils.mkdir_p(source_dir)
|
||||
end
|
||||
|
||||
after do
|
||||
FileUtils.rm_rf(path)
|
||||
FileUtils.rm_rf(source_dir)
|
||||
FileUtils.rm_rf(target_dir)
|
||||
FileUtils.rm_rf(archive_dir)
|
||||
FileUtils.remove_entry(tmpdir)
|
||||
end
|
||||
|
||||
shared_examples 'deletes symlinks' do |compression, decompression|
|
||||
it 'deletes the symlinks', :aggregate_failures do
|
||||
Dir.mkdir("#{tmpdir}/.git")
|
||||
Dir.mkdir("#{tmpdir}/folder")
|
||||
FileUtils.touch("#{tmpdir}/file.txt")
|
||||
FileUtils.touch("#{tmpdir}/folder/file.txt")
|
||||
FileUtils.touch("#{tmpdir}/.gitignore")
|
||||
FileUtils.touch("#{tmpdir}/.git/config")
|
||||
File.symlink('file.txt', "#{tmpdir}/.symlink")
|
||||
File.symlink('file.txt', "#{tmpdir}/.git/.symlink")
|
||||
File.symlink('file.txt', "#{tmpdir}/folder/.symlink")
|
||||
archive = File.join(archive_dir, 'archive')
|
||||
subject.public_send(compression, archive: archive, dir: tmpdir)
|
||||
Dir.mkdir("#{source_dir}/.git")
|
||||
Dir.mkdir("#{source_dir}/folder")
|
||||
FileUtils.touch("#{source_dir}/file.txt")
|
||||
FileUtils.touch("#{source_dir}/folder/file.txt")
|
||||
FileUtils.touch("#{source_dir}/.gitignore")
|
||||
FileUtils.touch("#{source_dir}/.git/config")
|
||||
File.symlink('file.txt', "#{source_dir}/.symlink")
|
||||
File.symlink('file.txt', "#{source_dir}/.git/.symlink")
|
||||
File.symlink('file.txt', "#{source_dir}/folder/.symlink")
|
||||
archive_file = File.join(archive_dir, 'symlink_archive.tar.gz')
|
||||
subject.public_send(compression, archive: archive_file, dir: source_dir)
|
||||
subject.public_send(decompression, archive: archive_file, dir: target_dir)
|
||||
|
||||
subject.public_send(decompression, archive: archive, dir: archive_dir)
|
||||
expect(File).to exist("#{target_dir}/file.txt")
|
||||
expect(File).to exist("#{target_dir}/folder/file.txt")
|
||||
expect(File).to exist("#{target_dir}/.gitignore")
|
||||
expect(File).to exist("#{target_dir}/.git/config")
|
||||
expect(File).not_to exist("#{target_dir}/.symlink")
|
||||
expect(File).not_to exist("#{target_dir}/.git/.symlink")
|
||||
expect(File).not_to exist("#{target_dir}/folder/.symlink")
|
||||
end
|
||||
end
|
||||
|
||||
expect(File.exist?("#{archive_dir}/file.txt")).to eq(true)
|
||||
expect(File.exist?("#{archive_dir}/folder/file.txt")).to eq(true)
|
||||
expect(File.exist?("#{archive_dir}/.gitignore")).to eq(true)
|
||||
expect(File.exist?("#{archive_dir}/.git/config")).to eq(true)
|
||||
expect(File.exist?("#{archive_dir}/.symlink")).to eq(false)
|
||||
expect(File.exist?("#{archive_dir}/.git/.symlink")).to eq(false)
|
||||
expect(File.exist?("#{archive_dir}/folder/.symlink")).to eq(false)
|
||||
shared_examples 'handles shared hard links' do |compression, decompression|
|
||||
let(:archive_file) { File.join(archive_dir, 'hard_link_archive.tar.gz') }
|
||||
|
||||
subject(:decompress) { mock_class.public_send(decompression, archive: archive_file, dir: target_dir) }
|
||||
|
||||
before do
|
||||
Dir.mkdir("#{source_dir}/dir")
|
||||
FileUtils.touch("#{source_dir}/file.txt")
|
||||
FileUtils.touch("#{source_dir}/dir/.file.txt")
|
||||
FileUtils.link("#{source_dir}/file.txt", "#{source_dir}/.hard_linked_file.txt")
|
||||
|
||||
mock_class.public_send(compression, archive: archive_file, dir: source_dir)
|
||||
end
|
||||
|
||||
it 'raises an exception and deletes the extraction dir', :aggregate_failures do
|
||||
expect(FileUtils).to receive(:remove_dir).with(target_dir).and_call_original
|
||||
expect(Dir).to exist(target_dir)
|
||||
expect { decompress }.to raise_error(described_class::HardLinkError)
|
||||
expect(Dir).not_to exist(target_dir)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -212,6 +236,8 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importe
|
|||
end
|
||||
|
||||
describe '#gzip' do
|
||||
let(:path) { source_dir }
|
||||
|
||||
it 'compresses specified file' do
|
||||
tempfile = Tempfile.new('test', path)
|
||||
filename = File.basename(tempfile.path)
|
||||
|
|
@ -229,14 +255,16 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importe
|
|||
end
|
||||
|
||||
describe '#gunzip' do
|
||||
let(:path) { source_dir }
|
||||
|
||||
it 'decompresses specified file' do
|
||||
filename = 'labels.ndjson.gz'
|
||||
gz_filepath = "spec/fixtures/bulk_imports/gz/#{filename}"
|
||||
FileUtils.copy_file(gz_filepath, File.join(tmpdir, filename))
|
||||
FileUtils.copy_file(gz_filepath, File.join(path, filename))
|
||||
|
||||
subject.gunzip(dir: tmpdir, filename: filename)
|
||||
subject.gunzip(dir: path, filename: filename)
|
||||
|
||||
expect(File.exist?(File.join(tmpdir, 'labels.ndjson'))).to eq(true)
|
||||
expect(File.exist?(File.join(path, 'labels.ndjson'))).to eq(true)
|
||||
end
|
||||
|
||||
context 'when exception occurs' do
|
||||
|
|
@ -250,7 +278,7 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importe
|
|||
it 'archives a folder without compression' do
|
||||
archive_file = File.join(archive_dir, 'archive.tar')
|
||||
|
||||
result = subject.tar_cf(archive: archive_file, dir: tmpdir)
|
||||
result = subject.tar_cf(archive: archive_file, dir: source_dir)
|
||||
|
||||
expect(result).to eq(true)
|
||||
expect(File.exist?(archive_file)).to eq(true)
|
||||
|
|
@ -270,29 +298,35 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importe
|
|||
end
|
||||
|
||||
describe '#untar_zxf' do
|
||||
let(:tar_archive_fixture) { 'spec/fixtures/symlink_export.tar.gz' }
|
||||
|
||||
it_behaves_like 'deletes symlinks', :tar_czf, :untar_zxf
|
||||
it_behaves_like 'handles shared hard links', :tar_czf, :untar_zxf
|
||||
|
||||
it 'has the right mask for project.json' do
|
||||
subject.untar_zxf(archive: archive, dir: path)
|
||||
subject.untar_zxf(archive: tar_archive_fixture, dir: target_dir)
|
||||
|
||||
expect(file_permissions("#{path}/project.json")).to eq(0755) # originally 777
|
||||
expect(file_permissions("#{target_dir}/project.json")).to eq(0755) # originally 777
|
||||
end
|
||||
|
||||
it 'has the right mask for uploads' do
|
||||
subject.untar_zxf(archive: archive, dir: path)
|
||||
subject.untar_zxf(archive: tar_archive_fixture, dir: target_dir)
|
||||
|
||||
expect(file_permissions("#{path}/uploads")).to eq(0755) # originally 555
|
||||
expect(file_permissions("#{target_dir}/uploads")).to eq(0755) # originally 555
|
||||
end
|
||||
end
|
||||
|
||||
describe '#untar_xf' do
|
||||
let(:tar_archive_fixture) { 'spec/fixtures/symlink_export.tar.gz' }
|
||||
|
||||
it_behaves_like 'deletes symlinks', :tar_cf, :untar_xf
|
||||
it_behaves_like 'handles shared hard links', :tar_cf, :untar_xf
|
||||
|
||||
it 'extracts archive without decompression' do
|
||||
filename = 'archive.tar.gz'
|
||||
archive_file = File.join(archive_dir, 'archive.tar')
|
||||
|
||||
FileUtils.copy_file(archive, File.join(archive_dir, filename))
|
||||
FileUtils.copy_file(tar_archive_fixture, File.join(archive_dir, filename))
|
||||
subject.gunzip(dir: archive_dir, filename: filename)
|
||||
|
||||
result = subject.untar_xf(archive: archive_file, dir: archive_dir)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator do
|
||||
RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator, feature_category: :importers do
|
||||
let_it_be(:filepath) { File.join(Dir.tmpdir, 'decompressed_archive_size_validator_spec.gz') }
|
||||
|
||||
before_all do
|
||||
|
|
@ -121,7 +121,7 @@ RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator do
|
|||
|
||||
context 'which archive path is a symlink' do
|
||||
let(:filepath) { File.join(Dir.tmpdir, 'symlink') }
|
||||
let(:error_message) { 'Archive path is a symlink' }
|
||||
let(:error_message) { 'Archive path is a symlink or hard link' }
|
||||
|
||||
before do
|
||||
FileUtils.ln_s(filepath, filepath, force: true)
|
||||
|
|
@ -132,6 +132,19 @@ RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when archive path shares multiple hard links' do
|
||||
let(:filesize) { 32 }
|
||||
let(:error_message) { 'Archive path is a symlink or hard link' }
|
||||
|
||||
before do
|
||||
FileUtils.link(filepath, File.join(Dir.mktmpdir, 'hard_link'))
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
expect(subject).not_to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context 'when archive path is not a file' do
|
||||
let(:filepath) { Dir.mktmpdir }
|
||||
let(:filesize) { File.size(filepath) }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::ImportExport::FileImporter do
|
||||
RSpec.describe Gitlab::ImportExport::FileImporter, feature_category: :importers do
|
||||
include ExportFileHelper
|
||||
|
||||
let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
|
||||
|
|
@ -113,28 +113,73 @@ RSpec.describe Gitlab::ImportExport::FileImporter do
|
|||
end
|
||||
|
||||
context 'error' do
|
||||
subject(:import) { described_class.import(importable: build(:project), archive_file: '', shared: shared) }
|
||||
|
||||
before do
|
||||
allow_next_instance_of(described_class) do |instance|
|
||||
allow(instance).to receive(:wait_for_archived_file).and_raise(StandardError)
|
||||
allow(instance).to receive(:wait_for_archived_file).and_raise(StandardError, 'foo')
|
||||
end
|
||||
described_class.import(importable: build(:project), archive_file: '', shared: shared)
|
||||
end
|
||||
|
||||
it 'removes symlinks in root folder' do
|
||||
import
|
||||
|
||||
expect(File.exist?(symlink_file)).to be false
|
||||
end
|
||||
|
||||
it 'removes hidden symlinks in root folder' do
|
||||
import
|
||||
|
||||
expect(File.exist?(hidden_symlink_file)).to be false
|
||||
end
|
||||
|
||||
it 'removes symlinks in subfolders' do
|
||||
import
|
||||
|
||||
expect(File.exist?(subfolder_symlink_file)).to be false
|
||||
end
|
||||
|
||||
it 'does not remove a valid file' do
|
||||
import
|
||||
|
||||
expect(File.exist?(valid_file)).to be true
|
||||
end
|
||||
|
||||
it 'returns false and sets an error on shared' do
|
||||
result = import
|
||||
|
||||
expect(result).to eq(false)
|
||||
expect(shared.errors.join).to eq('foo')
|
||||
end
|
||||
|
||||
context 'when files in the archive share hard links' do
|
||||
let(:hard_link_file) { "#{shared.export_path}/hard_link_file.txt" }
|
||||
|
||||
before do
|
||||
FileUtils.link(valid_file, hard_link_file)
|
||||
end
|
||||
|
||||
it 'returns false and sets an error on shared' do
|
||||
result = import
|
||||
|
||||
expect(result).to eq(false)
|
||||
expect(shared.errors.join).to eq('File shares hard link')
|
||||
end
|
||||
|
||||
it 'removes all files in export path' do
|
||||
expect(Dir).to exist(shared.export_path)
|
||||
expect(File).to exist(symlink_file)
|
||||
expect(File).to exist(hard_link_file)
|
||||
expect(File).to exist(valid_file)
|
||||
|
||||
import
|
||||
|
||||
expect(File).not_to exist(symlink_file)
|
||||
expect(File).not_to exist(hard_link_file)
|
||||
expect(File).not_to exist(valid_file)
|
||||
expect(Dir).not_to exist(shared.export_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when file exceeds acceptable decompressed size' do
|
||||
|
|
@ -157,8 +202,10 @@ RSpec.describe Gitlab::ImportExport::FileImporter do
|
|||
allow(Gitlab::ImportExport::DecompressedArchiveSizeValidator).to receive(:max_bytes).and_return(1)
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
expect(subject.import).to eq(false)
|
||||
it 'returns false and sets an error on shared' do
|
||||
result = subject.import
|
||||
|
||||
expect(result).to eq(false)
|
||||
expect(shared.errors.join).to eq('Decompressed archive size validation failed.')
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -35,16 +35,22 @@ RSpec.describe Gitlab::ImportExport::Json::NdjsonReader, feature_category: :impo
|
|||
expect(subject).to eq(root_tree)
|
||||
end
|
||||
|
||||
context 'when project.json is symlink' do
|
||||
it 'raises error an error' do
|
||||
Dir.mktmpdir do |tmpdir|
|
||||
FileUtils.touch(File.join(tmpdir, 'passwd'))
|
||||
File.symlink(File.join(tmpdir, 'passwd'), File.join(tmpdir, 'project.json'))
|
||||
context 'when project.json is symlink or hard link' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
ndjson_reader = described_class.new(tmpdir)
|
||||
where(:link_method) { [:link, :symlink] }
|
||||
|
||||
expect { ndjson_reader.consume_attributes(importable_path) }
|
||||
.to raise_error(Gitlab::ImportExport::Error, 'Invalid file')
|
||||
with_them do
|
||||
it 'raises an error' do
|
||||
Dir.mktmpdir do |tmpdir|
|
||||
FileUtils.touch(File.join(tmpdir, 'passwd'))
|
||||
FileUtils.send(link_method, File.join(tmpdir, 'passwd'), File.join(tmpdir, 'project.json'))
|
||||
|
||||
ndjson_reader = described_class.new(tmpdir)
|
||||
|
||||
expect { ndjson_reader.consume_attributes(importable_path) }
|
||||
.to raise_error(Gitlab::ImportExport::Error, 'Invalid file')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -97,18 +103,24 @@ RSpec.describe Gitlab::ImportExport::Json::NdjsonReader, feature_category: :impo
|
|||
end
|
||||
end
|
||||
|
||||
context 'when relation file is a symlink' do
|
||||
it 'yields nothing to the Enumerator' do
|
||||
Dir.mktmpdir do |tmpdir|
|
||||
Dir.mkdir(File.join(tmpdir, 'project'))
|
||||
File.write(File.join(tmpdir, 'passwd'), "{}\n{}")
|
||||
File.symlink(File.join(tmpdir, 'passwd'), File.join(tmpdir, 'project', 'issues.ndjson'))
|
||||
context 'when relation file is a symlink or hard link' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
ndjson_reader = described_class.new(tmpdir)
|
||||
where(:link_method) { [:link, :symlink] }
|
||||
|
||||
result = ndjson_reader.consume_relation(importable_path, 'issues')
|
||||
with_them do
|
||||
it 'yields nothing to the Enumerator' do
|
||||
Dir.mktmpdir do |tmpdir|
|
||||
Dir.mkdir(File.join(tmpdir, 'project'))
|
||||
File.write(File.join(tmpdir, 'passwd'), "{}\n{}")
|
||||
FileUtils.send(link_method, File.join(tmpdir, 'passwd'), File.join(tmpdir, 'project', 'issues.ndjson'))
|
||||
|
||||
expect(result.to_a).to eq([])
|
||||
ndjson_reader = described_class.new(tmpdir)
|
||||
|
||||
result = ndjson_reader.consume_relation(importable_path, 'issues')
|
||||
|
||||
expect(result.to_a).to eq([])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,15 +4,17 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe Gitlab::ImportExport::RecursiveMergeFolders do
|
||||
describe '.merge' do
|
||||
it 'merge folder and ignore symlinks' do
|
||||
it 'merges folder and ignores symlinks and files that share hard links' do
|
||||
Dir.mktmpdir do |tmpdir|
|
||||
source = "#{tmpdir}/source"
|
||||
FileUtils.mkdir_p("#{source}/folder/folder")
|
||||
FileUtils.touch("#{source}/file1.txt")
|
||||
FileUtils.touch("#{source}/file_that_shares_hard_links.txt")
|
||||
FileUtils.touch("#{source}/folder/file2.txt")
|
||||
FileUtils.touch("#{source}/folder/folder/file3.txt")
|
||||
FileUtils.ln_s("#{source}/file1.txt", "#{source}/symlink-file1.txt")
|
||||
FileUtils.ln_s("#{source}/folder", "#{source}/symlink-folder")
|
||||
FileUtils.link("#{source}/file_that_shares_hard_links.txt", "#{source}/hard_link.txt")
|
||||
|
||||
target = "#{tmpdir}/target"
|
||||
FileUtils.mkdir_p("#{target}/folder/folder")
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do
|
|||
project.update_pages_deployment!(create(:pages_deployment, project: project))
|
||||
end
|
||||
|
||||
before do
|
||||
stub_pages_setting(host: 'example.com')
|
||||
end
|
||||
|
||||
it 'returns nil when host is empty' do
|
||||
expect(described_class.new(nil).execute).to be_nil
|
||||
expect(described_class.new('').execute).to be_nil
|
||||
|
|
@ -69,7 +73,7 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do
|
|||
end
|
||||
|
||||
it 'returns the virual domain with no lookup_paths' do
|
||||
virtual_domain = described_class.new("#{project.namespace.path}.#{Settings.pages.host}").execute
|
||||
virtual_domain = described_class.new("#{project.namespace.path}.example.com").execute
|
||||
|
||||
expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain)
|
||||
expect(virtual_domain.cache_key).to match(/pages_domain_for_namespace_#{project.namespace.id}_/)
|
||||
|
|
@ -82,7 +86,7 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do
|
|||
end
|
||||
|
||||
it 'returns the virual domain with no lookup_paths' do
|
||||
virtual_domain = described_class.new("#{project.namespace.path}.#{Settings.pages.host}".downcase).execute
|
||||
virtual_domain = described_class.new("#{project.namespace.path}.example.com".downcase).execute
|
||||
|
||||
expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain)
|
||||
expect(virtual_domain.cache_key).to be_nil
|
||||
|
|
@ -104,7 +108,7 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do
|
|||
end
|
||||
|
||||
it 'returns the virual domain when there are pages deployed for the project' do
|
||||
virtual_domain = described_class.new("#{project.namespace.path}.#{Settings.pages.host}").execute
|
||||
virtual_domain = described_class.new("#{project.namespace.path}.example.com").execute
|
||||
|
||||
expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain)
|
||||
expect(virtual_domain.cache_key).to match(/pages_domain_for_namespace_#{project.namespace.id}_/)
|
||||
|
|
@ -113,7 +117,7 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do
|
|||
end
|
||||
|
||||
it 'finds domain with case-insensitive' do
|
||||
virtual_domain = described_class.new("#{project.namespace.path}.#{Settings.pages.host.upcase}").execute
|
||||
virtual_domain = described_class.new("#{project.namespace.path}.Example.com").execute
|
||||
|
||||
expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain)
|
||||
expect(virtual_domain.cache_key).to match(/pages_domain_for_namespace_#{project.namespace.id}_/)
|
||||
|
|
@ -127,7 +131,7 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do
|
|||
end
|
||||
|
||||
it 'returns the virual domain when there are pages deployed for the project' do
|
||||
virtual_domain = described_class.new("#{project.namespace.path}.#{Settings.pages.host}").execute
|
||||
virtual_domain = described_class.new("#{project.namespace.path}.example.com").execute
|
||||
|
||||
expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain)
|
||||
expect(virtual_domain.cache_key).to be_nil
|
||||
|
|
@ -143,7 +147,7 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do
|
|||
project.project_setting.update!(pages_unique_domain: 'unique-domain')
|
||||
end
|
||||
|
||||
subject(:virtual_domain) { described_class.new("unique-domain.#{Settings.pages.host.upcase}").execute }
|
||||
subject(:virtual_domain) { described_class.new('unique-domain.example.com').execute }
|
||||
|
||||
context 'when pages unique domain is enabled' do
|
||||
before_all do
|
||||
|
|
@ -171,6 +175,19 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do
|
|||
expect(virtual_domain.lookup_paths.first.project_id).to eq(project.id)
|
||||
end
|
||||
|
||||
context 'when a project path conflicts with a unique domain' do
|
||||
it 'prioritizes the unique domain project' do
|
||||
group = create(:group, path: 'unique-domain')
|
||||
other_project = build(:project, path: 'unique-domain.example.com', group: group)
|
||||
other_project.save!(validate: false)
|
||||
other_project.update_pages_deployment!(create(:pages_deployment, project: other_project))
|
||||
other_project.mark_pages_as_deployed
|
||||
|
||||
expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain)
|
||||
expect(virtual_domain.lookup_paths.first.project_id).to eq(project.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when :cache_pages_domain_api is disabled' do
|
||||
before do
|
||||
stub_feature_flags(cache_pages_domain_api: false)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe Gitlab::Plantuml, feature_category: :shared do
|
||||
describe ".configure" do
|
||||
subject { described_class.configure }
|
||||
|
||||
let(:plantuml_url) { "http://plantuml.foo.bar" }
|
||||
|
||||
before do
|
||||
stub_application_setting(plantuml_url: plantuml_url)
|
||||
end
|
||||
|
||||
context "when PlantUML is enabled" do
|
||||
before do
|
||||
allow(Gitlab::CurrentSettings).to receive(:plantuml_enabled).and_return(true)
|
||||
end
|
||||
|
||||
it "configures the endpoint URL" do
|
||||
expect(subject.url).to eq(plantuml_url)
|
||||
end
|
||||
|
||||
it "enables PNG support" do
|
||||
expect(subject.png_enable).to be_truthy
|
||||
end
|
||||
|
||||
it "disables SVG support" do
|
||||
expect(subject.svg_enable).to be_falsey
|
||||
end
|
||||
|
||||
it "disables TXT support" do
|
||||
expect(subject.txt_enable).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context "when PlantUML is disabled" do
|
||||
before do
|
||||
allow(Gitlab::CurrentSettings).to receive(:plantuml_enabled).and_return(false)
|
||||
end
|
||||
|
||||
it "configures the endpoint URL" do
|
||||
expect(subject.url).to eq(plantuml_url)
|
||||
end
|
||||
|
||||
it "enables PNG support" do
|
||||
expect(subject.png_enable).to be_falsey
|
||||
end
|
||||
|
||||
it "disables SVG support" do
|
||||
expect(subject.svg_enable).to be_falsey
|
||||
end
|
||||
|
||||
it "disables TXT support" do
|
||||
expect(subject.txt_enable).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Redis::EtagCache, feature_category: :shared do
|
||||
# Note: this is a pseudo-store in front of `Cache`, meant only as a tool
|
||||
# to move away from `SharedState` for etag cache data. Thus, we use the
|
||||
# same store configuration as the former.
|
||||
let(:instance_specific_config_file) { "config/redis.cache.yml" }
|
||||
|
||||
include_examples "redis_shared_examples"
|
||||
|
||||
describe '#pool' do
|
||||
let(:config_new_format_host) { "spec/fixtures/config/redis_new_format_host.yml" }
|
||||
let(:config_new_format_socket) { "spec/fixtures/config/redis_new_format_socket.yml" }
|
||||
let(:rails_root) { mktmpdir }
|
||||
|
||||
subject { described_class.pool }
|
||||
|
||||
before do
|
||||
# Override rails root to avoid having our fixtures overwritten by `redis.yml` if it exists
|
||||
allow(Gitlab::Redis::SharedState).to receive(:rails_root).and_return(rails_root)
|
||||
allow(Gitlab::Redis::Cache).to receive(:rails_root).and_return(rails_root)
|
||||
|
||||
allow(Gitlab::Redis::SharedState).to receive(:config_file_name).and_return(config_new_format_host)
|
||||
allow(Gitlab::Redis::Cache).to receive(:config_file_name).and_return(config_new_format_socket)
|
||||
end
|
||||
|
||||
around do |example|
|
||||
clear_pool
|
||||
example.run
|
||||
ensure
|
||||
clear_pool
|
||||
end
|
||||
|
||||
it 'instantiates an instance of MultiStore' do
|
||||
subject.with do |redis_instance|
|
||||
expect(redis_instance).to be_instance_of(::Gitlab::Redis::MultiStore)
|
||||
|
||||
expect(redis_instance.primary_store.connection[:id]).to eq("unix:///path/to/redis.sock/0")
|
||||
expect(redis_instance.secondary_store.connection[:id]).to eq("redis://test-host:6379/99")
|
||||
|
||||
expect(redis_instance.instance_name).to eq('EtagCache')
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'multi store feature flags', :use_primary_and_secondary_stores_for_etag_cache,
|
||||
:use_primary_store_as_default_for_etag_cache
|
||||
end
|
||||
|
||||
describe '#store_name' do
|
||||
it 'returns the name of the Cache store' do
|
||||
expect(described_class.store_name).to eq('Cache')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Utils::FileInfo, feature_category: :shared do
|
||||
let(:tmpdir) { Dir.mktmpdir }
|
||||
let(:file_path) { "#{tmpdir}/test.txt" }
|
||||
|
||||
before do
|
||||
FileUtils.touch(file_path)
|
||||
end
|
||||
|
||||
after do
|
||||
FileUtils.rm_rf(tmpdir)
|
||||
end
|
||||
|
||||
describe '.linked?' do
|
||||
it 'raises an error when file does not exist' do
|
||||
expect { subject.linked?('foo') }.to raise_error(Errno::ENOENT)
|
||||
end
|
||||
|
||||
shared_examples 'identifies a linked file' do
|
||||
it 'returns false when file or dir is not a link' do
|
||||
expect(subject.linked?(tmpdir)).to eq(false)
|
||||
expect(subject.linked?(file)).to eq(false)
|
||||
end
|
||||
|
||||
it 'returns true when file or dir is symlinked' do
|
||||
FileUtils.symlink(tmpdir, "#{tmpdir}/symlinked_dir")
|
||||
FileUtils.symlink(file_path, "#{tmpdir}/symlinked_file.txt")
|
||||
|
||||
expect(subject.linked?("#{tmpdir}/symlinked_dir")).to eq(true)
|
||||
expect(subject.linked?("#{tmpdir}/symlinked_file.txt")).to eq(true)
|
||||
end
|
||||
|
||||
it 'returns true when file has more than one hard link' do
|
||||
FileUtils.link(file_path, "#{tmpdir}/hardlinked_file.txt")
|
||||
|
||||
expect(subject.linked?(file)).to eq(true)
|
||||
expect(subject.linked?("#{tmpdir}/hardlinked_file.txt")).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when file is a File::Stat' do
|
||||
let(:file) { File.lstat(file_path) }
|
||||
|
||||
it_behaves_like 'identifies a linked file'
|
||||
end
|
||||
|
||||
context 'when file is path' do
|
||||
let(:file) { file_path }
|
||||
|
||||
it_behaves_like 'identifies a linked file'
|
||||
end
|
||||
end
|
||||
|
||||
describe '.shares_hard_link?' do
|
||||
it 'raises an error when file does not exist' do
|
||||
expect { subject.shares_hard_link?('foo') }.to raise_error(Errno::ENOENT)
|
||||
end
|
||||
|
||||
shared_examples 'identifies a file that shares a hard link' do
|
||||
it 'returns false when file or dir does not share hard links' do
|
||||
expect(subject.shares_hard_link?(tmpdir)).to eq(false)
|
||||
expect(subject.shares_hard_link?(file)).to eq(false)
|
||||
end
|
||||
|
||||
it 'returns true when file has more than one hard link' do
|
||||
FileUtils.link(file_path, "#{tmpdir}/hardlinked_file.txt")
|
||||
|
||||
expect(subject.shares_hard_link?(file)).to eq(true)
|
||||
expect(subject.shares_hard_link?("#{tmpdir}/hardlinked_file.txt")).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when file is a File::Stat' do
|
||||
let(:file) { File.lstat(file_path) }
|
||||
|
||||
it_behaves_like 'identifies a file that shares a hard link'
|
||||
end
|
||||
|
||||
context 'when file is path' do
|
||||
let(:file) { file_path }
|
||||
|
||||
it_behaves_like 'identifies a file that shares a hard link'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -3,13 +3,14 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Milestoneable do
|
||||
let(:user) { create(:user) }
|
||||
let(:milestone) { create(:milestone, project: project) }
|
||||
let_it_be(:group, reload: true) { create(:group) }
|
||||
let_it_be(:project) { create(:project, :repository, group: group) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:milestone) { create(:milestone, project: project) }
|
||||
|
||||
shared_examples_for 'an object that can be assigned a milestone' do
|
||||
describe 'Validation' do
|
||||
describe 'milestone' do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:milestone_id) { milestone.id }
|
||||
|
||||
subject { milestoneable_class.new(params) }
|
||||
|
|
@ -39,8 +40,6 @@ RSpec.describe Milestoneable do
|
|||
end
|
||||
|
||||
describe '#milestone_available?' do
|
||||
let(:group) { create(:group) }
|
||||
let(:project) { create(:project, group: group) }
|
||||
let(:issue) { create(:issue, project: project) }
|
||||
|
||||
def build_milestoneable(milestone_id)
|
||||
|
|
@ -62,9 +61,9 @@ RSpec.describe Milestoneable do
|
|||
it 'returns true with a milestone from the the parent of the issue project group' do
|
||||
parent = create(:group)
|
||||
group.update!(parent: parent)
|
||||
milestone = create(:milestone, group: parent)
|
||||
parent_milestone = create(:milestone, group: parent)
|
||||
|
||||
expect(build_milestoneable(milestone.id).milestone_available?).to be(true)
|
||||
expect(build_milestoneable(parent_milestone.id).milestone_available?).to be(true)
|
||||
end
|
||||
|
||||
it 'returns true with a blank milestone' do
|
||||
|
|
@ -86,9 +85,6 @@ RSpec.describe Milestoneable do
|
|||
end
|
||||
|
||||
describe '#supports_milestone?' do
|
||||
let(:group) { create(:group) }
|
||||
let(:project) { create(:project, group: group) }
|
||||
|
||||
context "for issues" do
|
||||
let(:issue) { build(:issue, project: project) }
|
||||
|
||||
|
|
@ -215,6 +211,15 @@ RSpec.describe Milestoneable do
|
|||
end
|
||||
|
||||
it_behaves_like 'an object that can be assigned a milestone'
|
||||
|
||||
describe '#milestone_available?' do
|
||||
it 'returns true with a milestone from the issue group' do
|
||||
milestone = create(:milestone, group: group)
|
||||
milestoneable = milestoneable_class.new(namespace: group, milestone_id: milestone.id)
|
||||
|
||||
expect(milestoneable.milestone_available?).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'MergeRequests' do
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue