Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-06-30 21:09:49 +00:00
parent 3707364380
commit 9877050db1
117 changed files with 2210 additions and 411 deletions

View File

@ -1957,7 +1957,6 @@ Layout/LineLength:
- 'ee/spec/features/groups/iterations/user_edits_iteration_spec.rb' - 'ee/spec/features/groups/iterations/user_edits_iteration_spec.rb'
- 'ee/spec/features/groups/iterations/user_views_iteration_cadence_spec.rb' - 'ee/spec/features/groups/iterations/user_views_iteration_cadence_spec.rb'
- 'ee/spec/features/groups/iterations/user_views_iteration_spec.rb' - 'ee/spec/features/groups/iterations/user_views_iteration_spec.rb'
- 'ee/spec/features/groups/members/manage_groups_spec.rb'
- 'ee/spec/features/groups/members/manage_members_spec.rb' - 'ee/spec/features/groups/members/manage_members_spec.rb'
- 'ee/spec/features/groups/members/override_ldap_memberships_spec.rb' - 'ee/spec/features/groups/members/override_ldap_memberships_spec.rb'
- 'ee/spec/features/groups/saml_providers_spec.rb' - 'ee/spec/features/groups/saml_providers_spec.rb'

View File

@ -2,6 +2,27 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 15.1.1 (2022-06-30)
### Security (16 changes)
- [Fix group IP restrictions not enforced for container registry requests](gitlab-org/security/gitlab@0c9628791bf383734ec8f32e1d0040ca2fd62178) ([merge request](gitlab-org/security/gitlab!2550))
- [Gitlab Runner version upgrade](gitlab-org/security/gitlab@b7e06c1e812fdf0a2fab4aca07cdea33ff22b41c) ([merge request](gitlab-org/security/gitlab!2564))
- [Update ProjectAttributesTransformer to use fixed number of attributes](gitlab-org/security/gitlab@fae2720ffd7ec5ce3eb88e3b68b2879f4f664cf4) ([merge request](gitlab-org/security/gitlab!2547))
- [Escape deploy key title to prevent XSS](gitlab-org/security/gitlab@071c3fa4ae63d03117a3c02752711d29f6f620b1) ([merge request](gitlab-org/security/gitlab!2492))
- [Sanitize ZenTao breadcrumb links](gitlab-org/security/gitlab@5b16b65cfe57a946f25842b7818dafe6c8a934ea) ([merge request](gitlab-org/security/gitlab!2555))
- [Fix permissions in the project labels API](gitlab-org/security/gitlab@b3ff7ee5a64382ff9ee34bc3fc44acd0117f86d9) ([merge request](gitlab-org/security/gitlab!2532))
- [Security fix sentry issue leaks and access level check](gitlab-org/security/gitlab@a0ad79588f170e1c58206e42d8b550d75e874a4d) ([merge request](gitlab-org/security/gitlab!2531))
- [Check permissions before exposing user two factor enabled](gitlab-org/security/gitlab@3b7c699ffcca64721c0876da12435c148f8e83a7) ([merge request](gitlab-org/security/gitlab!2530))
- [Filter milestone release by user access](gitlab-org/security/gitlab@dc79edc16c7422279235d2ad8a4807644840fc4c) ([merge request](gitlab-org/security/gitlab!2535))
- [Fix the required access level in the Conan packages finder](gitlab-org/security/gitlab@5221ca59f09361f90798348851fa12c91e5d9e35) ([merge request](gitlab-org/security/gitlab!2513))
- [Allow inviting only groups with subset of allowed domains to groups](gitlab-org/security/gitlab@03dfb153355d0465ea25a6d73db895c975fc32df) ([merge request](gitlab-org/security/gitlab!2538))
- [Fix open redirect vulnerability](gitlab-org/security/gitlab@eb52b11c7b29319d16e21feec97bafbdf0f3c3e5) ([merge request](gitlab-org/security/gitlab!2542))
- [Adds a filter based on user access to Runner jobs endpoint](gitlab-org/security/gitlab@a35c6aa42c35da96bf1df263b4a3aa1fe38af75d) ([merge request](gitlab-org/security/gitlab!2508))
- [Prevent runners from picking IP restricted jobs](gitlab-org/security/gitlab@9d6f0da89f6d2e8f3c7fbccea0d22fc6b17e0305) ([merge request](gitlab-org/security/gitlab!2505))
- [Restrict CI lint access to pipeline creators](gitlab-org/security/gitlab@bf15e9ceddf4b30105103defa50dd4a9094ac246) ([merge request](gitlab-org/security/gitlab!2516))
- [Catch endless headers when reading HTTP responses](gitlab-org/security/gitlab@d9a6ca9aa36cfd6dd916be2d4f1e8e25329ecc73) ([merge request](gitlab-org/security/gitlab!2527))
## 15.1.0 (2022-06-21) ## 15.1.0 (2022-06-21)
### Added (147 changes) ### Added (147 changes)
@ -963,6 +984,28 @@ entry.
- [Fix JH skipped subscription portal spec](gitlab-org/gitlab@0e7e7cb4a62d004989c47fafe6fe1f9ffd90da44) by @chaomao ([merge request](gitlab-org/gitlab!87213)) - [Fix JH skipped subscription portal spec](gitlab-org/gitlab@0e7e7cb4a62d004989c47fafe6fe1f9ffd90da44) by @chaomao ([merge request](gitlab-org/gitlab!87213))
- [Add not null constraint to requirements.issue_id validate:false](gitlab-org/gitlab@5ccac890b13c53c5761ccb8e5cb7ca202e0656c3) ([merge request](gitlab-org/gitlab!86590)) - [Add not null constraint to requirements.issue_id validate:false](gitlab-org/gitlab@5ccac890b13c53c5761ccb8e5cb7ca202e0656c3) ([merge request](gitlab-org/gitlab!86590))
## 15.0.4 (2022-06-30)
### Security (17 changes)
- [Fix group IP restrictions not enforced for container registry requests](gitlab-org/security/gitlab@7dea5867ea5e115a3a91576fec91de8e7f2a9915) ([merge request](gitlab-org/security/gitlab!2551))
- [Update rack gem to version 2.2.3.1](gitlab-org/security/gitlab@c0df8beef0297e9b99b954fcdcbf07cee3f0e9d6) ([merge request](gitlab-org/security/gitlab!2553))
- [Gitlab Runner version upgrade](gitlab-org/security/gitlab@012ff20c80754ff9ac38b82894346a51aa0a9b4c) ([merge request](gitlab-org/security/gitlab!2566))
- [Update ProjectAttributesTransformer to use fixed number of attributes](gitlab-org/security/gitlab@619d77865f3e61f3cfb6ca92011ded44f6baf0ad) ([merge request](gitlab-org/security/gitlab!2548))
- [Escape deploy key title to prevent XSS](gitlab-org/security/gitlab@7b1a458df5c553d6fa99b4fec0d677c9e924ad86) ([merge request](gitlab-org/security/gitlab!2493))
- [Sanitize ZenTao breadcrumb links](gitlab-org/security/gitlab@adb8b2829e3d6b69ea32a7524c6f772be1debf82) ([merge request](gitlab-org/security/gitlab!2556))
- [Fix permissions in the project labels API](gitlab-org/security/gitlab@4fd766e90ea6e8899897d7b7d9551b2edb5dce9a) ([merge request](gitlab-org/security/gitlab!2533))
- [Security fix sentry issue leaks and access level check](gitlab-org/security/gitlab@d43b2c600a5fc31592eb8f07a4fcfdf3141911f7) ([merge request](gitlab-org/security/gitlab!2500))
- [Check permissions before exposing user two factor enabled](gitlab-org/security/gitlab@aac30c9f3228efd643d3fc204ee49f740f1ebc81) ([merge request](gitlab-org/security/gitlab!2524))
- [Filter milestone release by user access](gitlab-org/security/gitlab@aa1b76b8eb2966463c8a10869e00f3320bf4ea1a) ([merge request](gitlab-org/security/gitlab!2536))
- [Fix the required access level in the Conan packages finder](gitlab-org/security/gitlab@fa090cd9d2adab46c6c3f2a70b351a61847b5c6c) ([merge request](gitlab-org/security/gitlab!2482))
- [Allow inviting only groups with subset of allowed domains to groups](gitlab-org/security/gitlab@981be1afc7c6bf8f699ced1ae930b201699e29e3) ([merge request](gitlab-org/security/gitlab!2511))
- [Fix open redirect vulnerability](gitlab-org/security/gitlab@fa9cf0a41f338e285701db231316897d362ce306) ([merge request](gitlab-org/security/gitlab!2541))
- [Adds a filter based on user access to Runner jobs endpoint](gitlab-org/security/gitlab@8be3da271d2a6ff3285846c50a5ce4dd584419ff) ([merge request](gitlab-org/security/gitlab!2496))
- [Prevent runners from picking IP restricted jobs](gitlab-org/security/gitlab@dcc830d14cc0ee616dc3ad263d66bd42f92b56a2) ([merge request](gitlab-org/security/gitlab!2504))
- [Restrict CI lint access to pipeline creators](gitlab-org/security/gitlab@42425cd68755c53ed33952111be9803ce3b37515) ([merge request](gitlab-org/security/gitlab!2514))
- [Catch endless headers when reading HTTP responses](gitlab-org/security/gitlab@d2ce0a236204b97a853bc35332d49d7427f38fbc) ([merge request](gitlab-org/security/gitlab!2528))
## 15.0.3 (2022-06-16) ## 15.0.3 (2022-06-16)
### Fixed (2 changes) ### Fixed (2 changes)
@ -1737,6 +1780,28 @@ entry.
- [Move methods to build email unsubscribe link to helper](gitlab-org/gitlab@ae4391a84d14d51ca5b5f2ffaada96e3b37a1d51) ([merge request](gitlab-org/gitlab!84696)) **GitLab Enterprise Edition** - [Move methods to build email unsubscribe link to helper](gitlab-org/gitlab@ae4391a84d14d51ca5b5f2ffaada96e3b37a1d51) ([merge request](gitlab-org/gitlab!84696)) **GitLab Enterprise Edition**
- [Deprecate `push_rules_supersede_code_owners` feature flag](gitlab-org/gitlab@9ee99872b66a69c5a2d1c1c9863d960832a1d91f) ([merge request](gitlab-org/gitlab!85390)) - [Deprecate `push_rules_supersede_code_owners` feature flag](gitlab-org/gitlab@9ee99872b66a69c5a2d1c1c9863d960832a1d91f) ([merge request](gitlab-org/gitlab!85390))
## 14.10.5 (2022-06-30)
### Security (17 changes)
- [Fix group IP restrictions not enforced for container registry requests](gitlab-org/security/gitlab@b146ad7b8c6fba9d3c5bea365ff8afd49949dcb0) ([merge request](gitlab-org/security/gitlab!2552))
- [Update rack gem to version 2.2.3.1](gitlab-org/security/gitlab@09ebb50ceee5a2226c1f70fa1d6c25391d51dda6) ([merge request](gitlab-org/security/gitlab!2554))
- [Gitlab Runner version upgrade](gitlab-org/security/gitlab@c91bfdb4f96e70e377a84b99c4edaa2fdecb8e16) ([merge request](gitlab-org/security/gitlab!2567))
- [Update ProjectAttributesTransformer to use fixed number of attributes](gitlab-org/security/gitlab@6f892fb2a4b84473c3796533551f915c16cf77d9) ([merge request](gitlab-org/security/gitlab!2549))
- [Escape deploy key title to prevent XSS](gitlab-org/security/gitlab@153a7c447e03a509b7f06ac7381f4f9db414c9ea) ([merge request](gitlab-org/security/gitlab!2494))
- [Sanitize ZenTao breadcrumb links](gitlab-org/security/gitlab@530c7be82ae90138898ff99008d994b1c85d8cf1) ([merge request](gitlab-org/security/gitlab!2557))
- [Fix permissions in the project labels API](gitlab-org/security/gitlab@f2c71f64c258bef9f56f4892d11a4dbf20d668e6) ([merge request](gitlab-org/security/gitlab!2534))
- [Security fix sentry issue leaks and access level check](gitlab-org/security/gitlab@c644d94f58e30e1a9d87521b039a347412f0fead) ([merge request](gitlab-org/security/gitlab!2501))
- [Check permissions before exposing user two factor enabled](gitlab-org/security/gitlab@8a623e8a4fdbd3421ac3ae0e37e156b7d3b04970) ([merge request](gitlab-org/security/gitlab!2525))
- [Filter milestone release by user access](gitlab-org/security/gitlab@d7d6431a52808107a71f15d29e856eef2cb313e5) ([merge request](gitlab-org/security/gitlab!2537))
- [Fix the required access level in the Conan packages finder](gitlab-org/security/gitlab@756fb242c4d6acf6cfd95fa39f37410eaf009747) ([merge request](gitlab-org/security/gitlab!2485))
- [Allow inviting only groups with subset of allowed domains to groups](gitlab-org/security/gitlab@ca50492a32a2e367b0bc75dae0f91dc52d23b2ed) ([merge request](gitlab-org/security/gitlab!2512))
- [Fix open redirect vulnerability](gitlab-org/security/gitlab@1450068a44d67af3cbe09fedcc4b1e9b4ea2e586) ([merge request](gitlab-org/security/gitlab!2540))
- [Adds a filter based on user access to Runner jobs endpoint](gitlab-org/security/gitlab@dafaf3e50e8b1a18ff362cbb60e9482c9d60fc33) ([merge request](gitlab-org/security/gitlab!2497))
- [Prevent runners from picking IP restricted jobs](gitlab-org/security/gitlab@0fad0cdde00b68c2a0f19ffa2681b438fcad4097) ([merge request](gitlab-org/security/gitlab!2503))
- [Restrict CI lint access to pipeline creators](gitlab-org/security/gitlab@c5b79e969f10e3604eff16a9edef716e700cd201) ([merge request](gitlab-org/security/gitlab!2515))
- [Catch endless headers when reading HTTP responses](gitlab-org/security/gitlab@65379002bd7a0259c425455c937b110bd96096dc) ([merge request](gitlab-org/security/gitlab!2529))
## 14.10.4 (2022-06-01) ## 14.10.4 (2022-06-01)
### Security (7 changes) ### Security (7 changes)

View File

@ -1,4 +1,4 @@
export const projectKeys = ['name', 'organizationName', 'organizationSlug', 'slug']; export const projectKeys = ['id', 'name', 'organizationName', 'organizationSlug', 'slug'];
export const transformFrontendSettings = ({ export const transformFrontendSettings = ({
apiHost, apiHost,
@ -9,6 +9,7 @@ export const transformFrontendSettings = ({
}) => { }) => {
const project = selectedProject const project = selectedProject
? { ? {
sentry_project_id: selectedProject.id,
slug: selectedProject.slug, slug: selectedProject.slug,
name: selectedProject.name, name: selectedProject.name,
organization_name: selectedProject.organizationName, organization_name: selectedProject.organizationName,

View File

@ -537,7 +537,7 @@ export default class AccessDropdown {
return ` return `
<li> <li>
<a href="#" class="${isActiveClass}"> <a href="#" class="${isActiveClass}">
<strong>${key.title}</strong> <strong>${escape(key.title)}</strong>
<p> <p>
${sprintf( ${sprintf(
__('Owned by %{image_tag}'), __('Owned by %{image_tag}'),

View File

@ -44,7 +44,7 @@ export default {
}, },
computed: { computed: {
buttonText() { buttonText() {
if (!this.hasSelectedColor()) { if (!this.hasSelectedColor) {
return this.dropdownButtonText; return this.dropdownButtonText;
} }
@ -103,7 +103,7 @@ export default {
:color="localSelectedColor.color" :color="localSelectedColor.color"
:title="localSelectedColor.title" :title="localSelectedColor.title"
/> />
<span v-else>{{ buttonText }}</span> <span v-else data-testid="fallback-button-text">{{ buttonText }}</span>
</template> </template>
<template #header> <template #header>
<dropdown-header <dropdown-header

View File

@ -4,6 +4,7 @@ class Projects::ErrorTrackingController < Projects::ErrorTracking::BaseControlle
respond_to :json respond_to :json
before_action :authorize_read_sentry_issue! before_action :authorize_read_sentry_issue!
before_action :authorize_update_sentry_issue!, only: %i[update]
before_action :set_issue_id, only: :details before_action :set_issue_id, only: :details
before_action only: [:index] do before_action only: [:index] do

View File

@ -129,7 +129,7 @@ module Projects
:integrated, :integrated,
:api_host, :api_host,
:token, :token,
project: [:slug, :name, :organization_slug, :organization_name] project: [:slug, :name, :organization_slug, :organization_name, :sentry_project_id]
], ],
grafana_integration_attributes: [:token, :grafana_url, :enabled] grafana_integration_attributes: [:token, :grafana_url, :enabled]

View File

@ -6,19 +6,29 @@ module Ci
ALLOWED_INDEXED_COLUMNS = %w[id].freeze ALLOWED_INDEXED_COLUMNS = %w[id].freeze
def initialize(runner, params = {}) def initialize(runner, current_user, params = {})
@runner = runner @runner = runner
@user = current_user
@params = params @params = params
end end
def execute def execute
items = @runner.builds items = @runner.builds
items = by_permission(items)
items = by_status(items) items = by_status(items)
sort_items(items) sort_items(items)
end end
private private
# rubocop: disable CodeReuse/ActiveRecord
def by_permission(items)
return items if @user.can_read_all_resources?
items.for_project(@user.authorized_project_mirrors(Gitlab::Access::REPORTER).select(:project_id))
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def by_status(items) def by_status(items)
return items unless Ci::HasStatus::AVAILABLE_STATUSES.include?(params[:status]) return items unless Ci::HasStatus::AVAILABLE_STATUSES.include?(params[:status])

View File

@ -25,7 +25,7 @@ module Packages
end end
def projects_visible_to_current_user def projects_visible_to_current_user
::Project.public_or_visible_to_user(current_user) ::Project.public_or_visible_to_user(current_user, ::Gitlab::Access::REPORTER)
end end
end end
end end

View File

@ -12,7 +12,7 @@ module Resolvers
Should not be requested more than once per request. Should not be requested more than once per request.
MD MD
authorize :read_pipeline authorize :create_pipeline
argument :project_path, GraphQL::Types::ID, argument :project_path, GraphQL::Types::ID,
required: true, required: true,

View File

@ -160,27 +160,6 @@ module IntegrationsHelper
!Gitlab.com? !Gitlab.com?
end end
def jira_issue_breadcrumb_link(issue_reference)
link_to '', { class: 'gl-display-flex gl-align-items-center gl-white-space-nowrap' } do
icon = image_tag image_path('illustrations/logos/jira.svg'), width: 15, height: 15, class: 'gl-mr-2'
[icon, html_escape(issue_reference)].join.html_safe
end
end
def zentao_issue_breadcrumb_link(issue)
link_to issue[:web_url], { target: '_blank', rel: 'noopener noreferrer', class: 'gl-display-flex gl-align-items-center gl-white-space-nowrap' } do
icon = image_tag image_path('logos/zentao.svg'), width: 15, height: 15, class: 'gl-mr-2'
[icon, html_escape(issue[:id])].join.html_safe
end
end
def zentao_issues_show_data
{
issues_show_path: project_integrations_zentao_issue_path(@project, params[:id], format: :json),
issues_list_path: project_integrations_zentao_issues_path(@project)
}
end
extend self extend self
private private

View File

@ -300,6 +300,7 @@ module ProjectsHelper
setting.organization_slug.blank? setting.organization_slug.blank?
{ {
sentry_project_id: setting.sentry_project_id,
name: setting.project_name, name: setting.project_name,
organization_name: setting.organization_name, organization_name: setting.organization_name,
organization_slug: setting.organization_slug, organization_slug: setting.organization_slug,

View File

@ -153,11 +153,11 @@ module TimeboxesHelper
n_("%{releases} release", "%{releases} releases", count) % { releases: count } n_("%{releases} release", "%{releases} releases", count) % { releases: count }
end end
def recent_releases_with_counts(milestone) def recent_releases_with_counts(milestone, user)
total_count = milestone.releases.size total_count = milestone.releases.size
return [[], 0, 0] if total_count == 0 return [[], 0, 0] if total_count == 0
recent_releases = milestone.releases.recent.to_a recent_releases = milestone.releases.recent.filter { |release| Ability.allowed?(user, :read_release, release) }
more_count = total_count - recent_releases.size more_count = total_count - recent_releases.size
[recent_releases, total_count, more_count] [recent_releases, total_count, more_count]
end end

View File

@ -171,6 +171,11 @@ class ApplicationSetting < ApplicationRecord
validates :kroki_formats, json_schema: { filename: 'application_setting_kroki_formats' } validates :kroki_formats, json_schema: { filename: 'application_setting_kroki_formats' }
validates :metrics_method_call_threshold,
numericality: { greater_than_or_equal_to: 0 },
presence: true,
if: :prometheus_metrics_enabled
validates :plantuml_url, validates :plantuml_url,
presence: true, presence: true,
if: :plantuml_enabled if: :plantuml_enabled

View File

@ -4,6 +4,8 @@ module Ci
# This model represents a shadow table of the main database's projects table. # This model represents a shadow table of the main database's projects table.
# It allows us to navigate the project and namespace hierarchy on the ci database. # It allows us to navigate the project and namespace hierarchy on the ci database.
class ProjectMirror < ApplicationRecord class ProjectMirror < ApplicationRecord
include FromUnion
belongs_to :project belongs_to :project
scope :by_namespace_id, -> (namespace_id) { where(namespace_id: namespace_id) } scope :by_namespace_id, -> (namespace_id) { where(namespace_id: namespace_id) }

View File

@ -3,7 +3,7 @@
module Clusters module Clusters
module Applications module Applications
class Runner < ApplicationRecord class Runner < ApplicationRecord
VERSION = '0.42.0' VERSION = '0.42.1'
self.table_name = 'clusters_applications_runners' self.table_name = 'clusters_applications_runners'

View File

@ -38,7 +38,8 @@ module Enums
bridge_pipeline_is_child_pipeline: 1_006, # not used anymore, but cannot be deleted because of old data bridge_pipeline_is_child_pipeline: 1_006, # not used anymore, but cannot be deleted because of old data
downstream_pipeline_creation_failed: 1_007, downstream_pipeline_creation_failed: 1_007,
secrets_provider_not_found: 1_008, secrets_provider_not_found: 1_008,
reached_max_descendant_pipelines_depth: 1_009 reached_max_descendant_pipelines_depth: 1_009,
ip_restriction_failure: 1_010
} }
end end
end end

View File

@ -5,9 +5,6 @@ class DeployToken < ApplicationRecord
include TokenAuthenticatable include TokenAuthenticatable
include PolicyActor include PolicyActor
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
include IgnorableColumns
ignore_column :token, remove_with: '15.2', remove_after: '2022-07-22'
add_authentication_token_field :token, encrypted: :required add_authentication_token_field :token, encrypted: :required

View File

@ -125,17 +125,22 @@ module ErrorTracking
def issue_details(opts = {}) def issue_details(opts = {})
with_reactive_cache('issue_details', opts.stringify_keys) do |result| with_reactive_cache('issue_details', opts.stringify_keys) do |result|
ensure_issue_belongs_to_project!(result[:issue].project_id)
result result
end end
end end
def issue_latest_event(opts = {}) def issue_latest_event(opts = {})
with_reactive_cache('issue_latest_event', opts.stringify_keys) do |result| with_reactive_cache('issue_latest_event', opts.stringify_keys) do |result|
ensure_issue_belongs_to_project!(result[:latest_event].project_id)
result result
end end
end end
def update_issue(opts = {}) def update_issue(opts = {})
issue_to_be_updated = sentry_client.issue_details(issue_id: opts[:issue_id])
ensure_issue_belongs_to_project!(issue_to_be_updated.project_id)
handle_exceptions do handle_exceptions do
{ updated: sentry_client.update_issue(opts) } { updated: sentry_client.update_issue(opts) }
end end
@ -177,6 +182,25 @@ module ErrorTracking
private private
def ensure_issue_belongs_to_project!(project_id_from_api)
raise 'The Sentry issue appers to be outside of the configured Sentry project' if Integer(project_id_from_api) != ensure_sentry_project_id!
end
def ensure_sentry_project_id!
return sentry_project_id if sentry_project_id.present?
raise("Couldn't find project: #{organization_name} / #{project_name} on Sentry") if sentry_project.nil?
update!(sentry_project_id: sentry_project.id)
sentry_project_id
end
def sentry_project
strong_memoize(:sentry_project) do
sentry_client.projects.find { |project| project.name == project_name && project.organization_name == organization_name }
end
end
def add_gitlab_issue_details(issue) def add_gitlab_issue_details(issue)
issue.gitlab_commit = match_gitlab_commit(issue.first_release_version) issue.gitlab_commit = match_gitlab_commit(issue.first_release_version)
issue.gitlab_commit_path = project_commit_path(project, issue.gitlab_commit) if issue.gitlab_commit issue.gitlab_commit_path = project_commit_path(project, issue.gitlab_commit) if issue.gitlab_commit

View File

@ -1657,6 +1657,14 @@ class User < ApplicationRecord
true true
end end
def authorized_project_mirrors(level)
projects = Ci::ProjectMirror.by_project_id(ci_project_mirrors_for_project_members(level))
namespace_projects = Ci::ProjectMirror.by_namespace_id(ci_namespace_mirrors_for_group_members(level).select(:namespace_id))
Ci::ProjectMirror.from_union([projects, namespace_projects])
end
def ci_owned_runners def ci_owned_runners
@ci_owned_runners ||= begin @ci_owned_runners ||= begin
Ci::Runner Ci::Runner
@ -2113,6 +2121,10 @@ class User < ApplicationRecord
end end
# rubocop: enable CodeReuse/ServiceClass # rubocop: enable CodeReuse/ServiceClass
def ci_project_mirrors_for_project_members(level)
project_members.where('access_level >= ?', level).pluck(:source_id)
end
def notification_email_verified def notification_email_verified
return if notification_email.blank? || temp_oauth_email? return if notification_email.blank? || temp_oauth_email?
@ -2250,7 +2262,7 @@ class User < ApplicationRecord
end end
def ci_owned_project_runners_from_project_members def ci_owned_project_runners_from_project_members
project_ids = project_members.where('access_level >= ?', Gitlab::Access::MAINTAINER).pluck(:source_id) project_ids = ci_project_mirrors_for_project_members(Gitlab::Access::MAINTAINER)
Ci::Runner Ci::Runner
.joins(:runner_projects) .joins(:runner_projects)

View File

@ -325,7 +325,6 @@ class ProjectPolicy < BasePolicy
enable :read_deployment enable :read_deployment
enable :read_merge_request enable :read_merge_request
enable :read_sentry_issue enable :read_sentry_issue
enable :update_sentry_issue
enable :read_prometheus enable :read_prometheus
enable :read_metrics_dashboard_annotation enable :read_metrics_dashboard_annotation
enable :metrics_dashboard enable :metrics_dashboard
@ -440,6 +439,7 @@ class ProjectPolicy < BasePolicy
enable :admin_feature_flags_user_lists enable :admin_feature_flags_user_lists
enable :update_escalation_status enable :update_escalation_status
enable :read_secure_files enable :read_secure_files
enable :update_sentry_issue
end end
rule { can?(:developer_access) & user_confirmed? }.policy do rule { can?(:developer_access) & user_confirmed? }.policy do

View File

@ -33,7 +33,8 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
trace_size_exceeded: 'The job log size limit was reached', trace_size_exceeded: 'The job log size limit was reached',
builds_disabled: 'The CI/CD is disabled for this project', builds_disabled: 'The CI/CD is disabled for this project',
environment_creation_failure: 'This job could not be executed because it would create an environment with an invalid parameter.', environment_creation_failure: 'This job could not be executed because it would create an environment with an invalid parameter.',
deployment_rejected: 'This deployment job was rejected.' deployment_rejected: 'This deployment job was rejected.',
ip_restriction_failure: "This job could not be executed because group IP address restrictions are enabled, and the runner's IP address is not in the allowed range."
}.freeze }.freeze
TROUBLESHOOTING_DOC = { TROUBLESHOOTING_DOC = {

View File

@ -16,7 +16,7 @@ class MemberUserEntity < UserEntity
user.blocked? user.blocked?
end end
expose :two_factor_enabled do |user| expose :two_factor_enabled, if: -> (user) { current_user_can_manage_members? || current_user?(user) } do |user|
user.two_factor_enabled? user.two_factor_enabled?
end end
@ -25,6 +25,18 @@ class MemberUserEntity < UserEntity
user.status.emoji user.status.emoji
end end
end end
private
def current_user_can_manage_members?
return false unless options[:source]
Ability.allowed?(options[:current_user], :"admin_#{options[:source].to_ability_name}_member", options[:source])
end
def current_user?(user)
options[:current_user] == user
end
end end
MemberUserEntity.prepend_mod_with('MemberUserEntity') MemberUserEntity.prepend_mod_with('MemberUserEntity')

View File

@ -89,7 +89,8 @@ module Projects
api_url: api_url, api_url: api_url,
enabled: settings[:enabled], enabled: settings[:enabled],
project_name: settings.dig(:project, :name), project_name: settings.dig(:project, :name),
organization_name: settings.dig(:project, :organization_name) organization_name: settings.dig(:project, :organization_name),
sentry_project_id: settings.dig(:project, :sentry_project_id)
} }
} }
params[:error_tracking_setting_attributes][:token] = settings[:token] unless /\A\*+\z/.match?(settings[:token]) # Don't update token if we receive masked value params[:error_tracking_setting_attributes][:token] = settings[:token] unless /\A\*+\z/.match?(settings[:token]) # Don't update token if we receive masked value

View File

@ -1,5 +1,5 @@
= gitlab_ui_form_for @application_setting, url: metrics_and_profiling_admin_application_settings_path(anchor: 'js-prometheus-settings'), html: { class: 'fieldset-form' } do |f| = gitlab_ui_form_for @application_setting, url: metrics_and_profiling_admin_application_settings_path(anchor: 'js-prometheus-settings'), html: { class: 'fieldset-form' } do |f|
= form_errors(@application_setting) = form_errors(@application_setting, pajamas_alert: true)
%fieldset %fieldset
.form-group .form-group

View File

@ -113,6 +113,8 @@
= render_if_exists 'admin/application_settings/slack' = render_if_exists 'admin/application_settings/slack'
-# this partial is from JiHu, see details in https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/417 -# this partial is from JiHu, see details in https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/417
= render_if_exists 'admin/application_settings/dingtalk_integration' = render_if_exists 'admin/application_settings/dingtalk_integration'
-# this partial is from JiHu, see details in https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/640
= render_if_exists 'admin/application_settings/feishu_integration'
= render 'admin/application_settings/third_party_offers' = render 'admin/application_settings/third_party_offers'
= render 'admin/application_settings/snowplow' = render 'admin/application_settings/snowplow'
= render 'admin/application_settings/eks' = render 'admin/application_settings/eks'

View File

@ -14,7 +14,7 @@
- if milestone.due_date || milestone.start_date - if milestone.due_date || milestone.start_date
.text-tertiary.gl-mb-2 .text-tertiary.gl-mb-2
= milestone_date_range(milestone) = milestone_date_range(milestone)
- recent_releases, total_count, more_count = recent_releases_with_counts(milestone) - recent_releases, total_count, more_count = recent_releases_with_counts(milestone, current_user)
- unless total_count == 0 - unless total_count == 0
.text-tertiary.gl-mb-2.milestone-release-links .text-tertiary.gl-mb-2.milestone-release-links
= sprite_icon("rocket", size: 12) = sprite_icon("rocket", size: 12)

View File

@ -138,7 +138,7 @@
= milestone.merge_requests.merged.count = milestone.merge_requests.merged.count
- if project - if project
- recent_releases, total_count, more_count = recent_releases_with_counts(milestone) - recent_releases, total_count, more_count = recent_releases_with_counts(milestone, current_user)
.block.releases .block.releases
.sidebar-collapsed-icon.has-tooltip{ title: milestone_releases_tooltip_text(milestone), data: { container: 'body', placement: 'left', boundary: 'viewport' } } .sidebar-collapsed-icon.has-tooltip{ title: milestone_releases_tooltip_text(milestone), data: { container: 'body', placement: 'left', boundary: 'viewport' } }
%strong %strong

View File

@ -1020,6 +1020,15 @@
:weight: 1 :weight: 1
:idempotent: false :idempotent: false
:tags: [] :tags: []
- :name: github_importer:github_import_import_issue_event
:worker_name: Gitlab::GithubImport::ImportIssueEventWorker
:feature_category: :importers
:has_external_dependencies: true
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent: false
:tags: []
- :name: github_importer:github_import_import_lfs_object - :name: github_importer:github_import_import_lfs_object
:worker_name: Gitlab::GithubImport::ImportLfsObjectWorker :worker_name: Gitlab::GithubImport::ImportLfsObjectWorker
:feature_category: :importers :feature_category: :importers
@ -1092,6 +1101,15 @@
:weight: 1 :weight: 1
:idempotent: false :idempotent: false
:tags: [] :tags: []
- :name: github_importer:github_import_stage_import_issue_events
:worker_name: Gitlab::GithubImport::Stage::ImportIssueEventsWorker
:feature_category: :importers
:has_external_dependencies: false
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent: false
:tags: []
- :name: github_importer:github_import_stage_import_issues_and_diff_notes - :name: github_importer:github_import_stage_import_issues_and_diff_notes
:worker_name: Gitlab::GithubImport::Stage::ImportIssuesAndDiffNotesWorker :worker_name: Gitlab::GithubImport::Stage::ImportIssuesAndDiffNotesWorker
:feature_category: :importers :feature_category: :importers

View File

@ -23,6 +23,7 @@ module Gitlab
pull_requests_merged_by: Stage::ImportPullRequestsMergedByWorker, pull_requests_merged_by: Stage::ImportPullRequestsMergedByWorker,
pull_request_reviews: Stage::ImportPullRequestsReviewsWorker, pull_request_reviews: Stage::ImportPullRequestsReviewsWorker,
issues_and_diff_notes: Stage::ImportIssuesAndDiffNotesWorker, issues_and_diff_notes: Stage::ImportIssuesAndDiffNotesWorker,
issue_events: Stage::ImportIssueEventsWorker,
notes: Stage::ImportNotesWorker, notes: Stage::ImportNotesWorker,
lfs_objects: Stage::ImportLfsObjectsWorker, lfs_objects: Stage::ImportLfsObjectsWorker,
finish: Stage::FinishImportWorker finish: Stage::FinishImportWorker

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
module Gitlab
module GithubImport
class ImportIssueEventWorker # rubocop:disable Scalability/IdempotentWorker
include ObjectImporter
def representation_class
Representation::IssueEvent
end
def importer_class
Importer::IssueEventImporter
end
def object_type
:issue_event
end
end
end
end

View File

@ -0,0 +1,47 @@
# frozen_string_literal: true
module Gitlab
module GithubImport
module Stage
class ImportIssueEventsWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
data_consistency :always
sidekiq_options retry: 3
include GithubImport::Queue
include StageMethods
# client - An instance of Gitlab::GithubImport::Client.
# project - An instance of Project.
def import(client, project)
importer = ::Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter
return skip_to_next_stage(project, importer) if feature_disabled?(project)
start_importer(project, importer, client)
end
private
def start_importer(project, importer, client)
info(project.id, message: "starting importer", importer: importer.name)
waiter = importer.new(project, client).execute
move_to_next_stage(project, waiter.key => waiter.jobs_remaining)
end
def skip_to_next_stage(project, importer)
info(project.id, message: "skipping importer", importer: importer.name)
move_to_next_stage(project)
end
def move_to_next_stage(project, waiters = {})
AdvanceStageWorker.perform_async(project.id, waiters, :notes)
end
def feature_disabled?(project)
Feature.disabled?(:github_importer_issue_events_import, project.group, type: :ops)
end
end
end
end
end

View File

@ -21,7 +21,7 @@ module Gitlab
hash[waiter.key] = waiter.jobs_remaining hash[waiter.key] = waiter.jobs_remaining
end end
AdvanceStageWorker.perform_async(project.id, waiters, :notes) AdvanceStageWorker.perform_async(project.id, waiters, :issue_events)
end end
# The importers to run in this stage. Issues can't be imported earlier # The importers to run in this stage. Issues can't be imported earlier

View File

@ -0,0 +1,8 @@
---
name: github_importer_issue_events_import
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89134
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/365977
milestone: '15.2'
type: ops
group: group::import
default_enabled: false

View File

@ -0,0 +1,46 @@
# frozen_string_literal: true
module Net
class HTTPResponse
# rubocop: disable Cop/LineBreakAfterGuardClauses
# rubocop: disable Cop/LineBreakAroundConditionalBlock
# rubocop: disable Layout/EmptyLineAfterGuardClause
# rubocop: disable Style/AndOr
# rubocop: disable Style/CharacterLiteral
# rubocop: disable Style/InfiniteLoop
# Original method:
# https://github.com/ruby/ruby/blob/v2_7_5/lib/net/http/response.rb#L54-L69
#
# Our changes:
# - Pass along the `start_time` to `Gitlab::BufferedIo`, so we can raise a timeout
# if reading the headers takes too long.
# - Limit the regexes to avoid ReDoS attacks.
def self.each_response_header(sock)
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
key = value = nil
while true
line = sock.is_a?(Gitlab::BufferedIo) ? sock.readuntil("\n", true, start_time) : sock.readuntil("\n", true)
line = line.sub(/\s{0,10}\z/, '')
break if line.empty?
if line[0] == ?\s or line[0] == ?\t and value
# :nocov:
value << ' ' unless value.empty?
value << line.strip
# :nocov:
else
yield key, value if key
key, value = line.strip.split(/\s{0,10}:\s{0,10}/, 2)
raise Net::HTTPBadResponse, 'wrong header line format' if value.nil?
end
end
yield key, value if key
end
# rubocop: enable Cop/LineBreakAfterGuardClauses
# rubocop: enable Cop/LineBreakAroundConditionalBlock
# rubocop: enable Layout/EmptyLineAfterGuardClause
# rubocop: enable Style/AndOr
# rubocop: enable Style/CharacterLiteral
# rubocop: enable Style/InfiniteLoop
end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
class AddInstallableConanPackagesIndexToPackages < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
INDEX_NAME = 'idx_installable_conan_pkgs_on_project_id_id'
# as defined by Packages::Package.package_types
CONAN_PACKAGE_TYPE = 3
# as defined by Packages::Package::INSTALLABLE_STATUSES
DEFAULT_STATUS = 0
HIDDEN_STATUS = 1
def up
where = "package_type = #{CONAN_PACKAGE_TYPE} AND status IN (#{DEFAULT_STATUS}, #{HIDDEN_STATUS})"
add_concurrent_index :packages_packages,
[:project_id, :id],
where: where,
name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :packages_packages, INDEX_NAME
end
end

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddSentryProjectIdToProjectErrorTrackingSettings < Gitlab::Database::Migration[2.0]
def change
add_column :project_error_tracking_settings, :sentry_project_id, :bigint
end
end

View File

@ -0,0 +1,12 @@
# frozen_string_literal: true
class AddFeiShuIntegration < Gitlab::Database::Migration[2.0]
def change
add_column :application_settings, :feishu_integration_enabled, :boolean, null: false,
default: false, comment: 'JiHu-specific column'
add_column :application_settings, :encrypted_feishu_app_key, :binary, comment: 'JiHu-specific column'
add_column :application_settings, :encrypted_feishu_app_key_iv, :binary, comment: 'JiHu-specific column'
add_column :application_settings, :encrypted_feishu_app_secret, :binary, comment: 'JiHu-specific column'
add_column :application_settings, :encrypted_feishu_app_secret_iv, :binary, comment: 'JiHu-specific column'
end
end

View File

@ -0,0 +1 @@
1fdb60b1c72b687aa8bede083ac7038097d538dc815e334d74296b1d39c2acb8

View File

@ -0,0 +1 @@
1f03beba0775e2a4eead512819592f590b02b70096cee250dfcdf426440cb5f5

View File

@ -0,0 +1 @@
80c35cd4dbc2e00e721ccb9313ff0f2f4f85e781c7961680e14769c308f067ed

View File

@ -11324,6 +11324,11 @@ CREATE TABLE application_settings (
phone_verification_code_enabled boolean DEFAULT false NOT NULL, phone_verification_code_enabled boolean DEFAULT false NOT NULL,
max_number_of_repository_downloads smallint DEFAULT 0 NOT NULL, max_number_of_repository_downloads smallint DEFAULT 0 NOT NULL,
max_number_of_repository_downloads_within_time_period integer DEFAULT 0 NOT NULL, max_number_of_repository_downloads_within_time_period integer DEFAULT 0 NOT NULL,
feishu_integration_enabled boolean DEFAULT false NOT NULL,
encrypted_feishu_app_key bytea,
encrypted_feishu_app_key_iv bytea,
encrypted_feishu_app_secret bytea,
encrypted_feishu_app_secret_iv bytea,
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)), CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)), CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)),
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)), CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
@ -11386,6 +11391,16 @@ COMMENT ON COLUMN application_settings.encrypted_dingtalk_app_secret_iv IS 'JiHu
COMMENT ON COLUMN application_settings.phone_verification_code_enabled IS 'JiHu-specific column'; COMMENT ON COLUMN application_settings.phone_verification_code_enabled IS 'JiHu-specific column';
COMMENT ON COLUMN application_settings.feishu_integration_enabled IS 'JiHu-specific column';
COMMENT ON COLUMN application_settings.encrypted_feishu_app_key IS 'JiHu-specific column';
COMMENT ON COLUMN application_settings.encrypted_feishu_app_key_iv IS 'JiHu-specific column';
COMMENT ON COLUMN application_settings.encrypted_feishu_app_secret IS 'JiHu-specific column';
COMMENT ON COLUMN application_settings.encrypted_feishu_app_secret_iv IS 'JiHu-specific column';
CREATE SEQUENCE application_settings_id_seq CREATE SEQUENCE application_settings_id_seq
START WITH 1 START WITH 1
INCREMENT BY 1 INCREMENT BY 1
@ -19341,7 +19356,8 @@ CREATE TABLE project_error_tracking_settings (
encrypted_token_iv character varying, encrypted_token_iv character varying,
project_name character varying, project_name character varying,
organization_name character varying, organization_name character varying,
integrated boolean DEFAULT true NOT NULL integrated boolean DEFAULT true NOT NULL,
sentry_project_id bigint
); );
CREATE TABLE project_export_jobs ( CREATE TABLE project_export_jobs (
@ -26973,6 +26989,8 @@ CREATE UNIQUE INDEX idx_external_audit_event_destination_id_key_uniq ON audit_ev
CREATE INDEX idx_geo_con_rep_updated_events_on_container_repository_id ON geo_container_repository_updated_events USING btree (container_repository_id); CREATE INDEX idx_geo_con_rep_updated_events_on_container_repository_id ON geo_container_repository_updated_events USING btree (container_repository_id);
CREATE INDEX idx_installable_conan_pkgs_on_project_id_id ON packages_packages USING btree (project_id, id) WHERE ((package_type = 3) AND (status = ANY (ARRAY[0, 1])));
CREATE INDEX idx_installable_helm_pkgs_on_project_id_id ON packages_packages USING btree (project_id, id); CREATE INDEX idx_installable_helm_pkgs_on_project_id_id ON packages_packages USING btree (project_id, id);
CREATE INDEX idx_installable_npm_pkgs_on_project_id_name_version_id ON packages_packages USING btree (project_id, name, version, id) WHERE ((package_type = 2) AND (status = 0)); CREATE INDEX idx_installable_npm_pkgs_on_project_id_name_version_id ON packages_packages USING btree (project_id, name, version, id) WHERE ((package_type = 2) AND (status = 0));

View File

@ -269,6 +269,9 @@ control over how the Pages daemon runs and serves content in your environment.
| `max_uri_length` | The maximum length of URIs accepted by GitLab Pages. Set to 0 for unlimited length. [Introduced](https://gitlab.com/gitlab-org/gitlab-pages/-/issues/659) in GitLab 14.5. | `max_uri_length` | The maximum length of URIs accepted by GitLab Pages. Set to 0 for unlimited length. [Introduced](https://gitlab.com/gitlab-org/gitlab-pages/-/issues/659) in GitLab 14.5.
| `metrics_address` | The address to listen on for metrics requests. | | `metrics_address` | The address to listen on for metrics requests. |
| `redirect_http` | Redirect pages from HTTP to HTTPS, true/false. | | `redirect_http` | Redirect pages from HTTP to HTTPS, true/false. |
| `redirects_max_config_size` | The maximum size of the _redirects file, in bytes (default: 65536). |
| `redirects_max_path_segments` | The maximum number of path segments allowed in _redirects rules URLs (default: 25). |
| `redirects_max_rule_count` | The maximum number of rules allowed in _redirects (default: 1000). |
| `sentry_dsn` | The address for sending Sentry crash reporting to. | | `sentry_dsn` | The address for sending Sentry crash reporting to. |
| `sentry_enabled` | Enable reporting and logging with Sentry, true/false. | | `sentry_enabled` | Enable reporting and logging with Sentry, true/false. |
| `sentry_environment` | The environment for Sentry crash reporting. | | `sentry_environment` | The environment for Sentry crash reporting. |
@ -577,6 +580,19 @@ HTTP Strict Transport Security (HSTS) can be enabled through the `gitlab_pages['
gitlab_pages['headers'] = ['Strict-Transport-Security: max-age=63072000'] gitlab_pages['headers'] = ['Strict-Transport-Security: max-age=63072000']
``` ```
### Pages project redirects limits
> [Introduced](https://gitlab.com/gitlab-org/gitlab-pages/-/merge_requests/778) in GitLab 15.2.
GitLab Pages comes with a set of default limits for the [`_redirects` file](../../user/project/pages/redirects.md)
to minimize the impact on performance. You can configure these limits if you'd like to increase or decrease the limits.
```ruby
gitlab_pages['redirects_max_config_size'] = 131072
gitlab_pages['redirects_max_path_segments'] = 50
gitlab_pages['redirects_max_rule_count'] = 2000
```
## Activate verbose logging for daemon ## Activate verbose logging for daemon
Follow the steps below to configure verbose logging of GitLab Pages daemon. Follow the steps below to configure verbose logging of GitLab Pages daemon.

View File

@ -359,7 +359,8 @@ and will be removed in [GitLab 16.0](https://gitlab.com/gitlab-org/gitlab/-/issu
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/15432) in GitLab 10.3. > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/15432) in GitLab 10.3.
List jobs that are being processed or were processed by specified runner. List jobs that are being processed or were processed by the specified runner. The list of jobs is limited
to projects where the user has at least the Reporter role.
```plaintext ```plaintext
GET /runners/:id/jobs GET /runners/:id/jobs

View File

@ -106,7 +106,7 @@ button and a link to the GitLab issue displays within the error detail section.
## Taking Action on errors ## Taking Action on errors
You can take action on Sentry Errors from within the GitLab UI. You can take action on Sentry Errors from within the GitLab UI. Marking errors ignored or resolved require at least Developer role.
### Ignoring errors ### Ignoring errors

View File

@ -480,6 +480,9 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap
1. Ensure all GitLab web nodes are running GitLab 15.1.Z. 1. Ensure all GitLab web nodes are running GitLab 15.1.Z.
1. [Enable the `active_support_hash_digest_sha256` feature flag](../administration/feature_flags.md#how-to-enable-and-disable-features-behind-flags) to switch `ActiveSupport::Digest` to use SHA256: 1. [Enable the `active_support_hash_digest_sha256` feature flag](../administration/feature_flags.md#how-to-enable-and-disable-features-behind-flags) to switch `ActiveSupport::Digest` to use SHA256:
1. Only then, continue to upgrade to later versions of GitLab. 1. Only then, continue to upgrade to later versions of GitLab.
- Unauthenticated requests to the [`ciConfig` GraphQL field](../api/graphql/reference/index.md#queryciconfig) are no longer supported.
Before you upgrade to GitLab 15.1, add an [access token](../api/index.md#authentication) to your requests.
The user creating the token must have [permission](../user/permissions.md) to create pipelines in the project.
### 15.0.0 ### 15.0.0

View File

@ -649,6 +649,7 @@ at the group level.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7297) in GitLab 12.2. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7297) in GitLab 12.2.
> - Support for specifying multiple email domains [added](https://gitlab.com/gitlab-org/gitlab/-/issues/33143) in GitLab 13.1. > - Support for specifying multiple email domains [added](https://gitlab.com/gitlab-org/gitlab/-/issues/33143) in GitLab 13.1.
> - Support for restricting access to projects in the group [added](https://gitlab.com/gitlab-org/gitlab/-/issues/14004) in GitLab 14.1.2. > - Support for restricting access to projects in the group [added](https://gitlab.com/gitlab-org/gitlab/-/issues/14004) in GitLab 14.1.2.
> - Support for restricting group memberships to groups with a subset of the allowed email domains [added](https://gitlab.com/gitlab-org/gitlab/-/issues/354791) in GitLab 15.0.1
You can prevent users with email addresses in specific domains from being added to a group and its projects. You can prevent users with email addresses in specific domains from being added to a group and its projects.
@ -669,6 +670,8 @@ The most popular public email domains cannot be restricted, such as:
- `hotmail.com`, `hotmail.co.uk`, `hotmail.fr` - `hotmail.com`, `hotmail.co.uk`, `hotmail.fr`
- `msn.com`, `live.com`, `outlook.com` - `msn.com`, `live.com`, `outlook.com`
When you share a group, both the source and target namespaces must allow the domains of the members' email addresses.
## Restrict Git access protocols ## Restrict Git access protocols
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/365601) in GitLab 15.1 [with a flag](../../administration/feature_flags.md) named `group_level_git_protocol_control`. Disabled by default. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/365601) in GitLab 15.1 [with a flag](../../administration/feature_flags.md) named `group_level_git_protocol_control`. Disabled by default.

View File

@ -45,8 +45,9 @@ Note that:
- All paths must start with a forward slash `/`. - All paths must start with a forward slash `/`.
- A default status code of `301` is applied if no [status code](#http-status-codes) is provided. - A default status code of `301` is applied if no [status code](#http-status-codes) is provided.
- The `_redirects` file has a file size limit of 64KB and a maximum of 1,000 rules per project. - The `_redirects` file has a file size limit and a maximum number of rules per project,
Only the first 1,000 rules are processed. configured at the instance level. Only the first matching rules within the configured maximum are processed.
The default file size limit is 64KB, and the default maximum number of rules is 1,000.
- If your GitLab Pages site uses the default domain name (such as - If your GitLab Pages site uses the default domain name (such as
`namespace.gitlab.io/projectname`) you must prefix every rule with the project name: `namespace.gitlab.io/projectname`) you must prefix every rule with the project name:

View File

@ -128,7 +128,7 @@ module API
runner = get_runner(params[:id]) runner = get_runner(params[:id])
authenticate_list_runners_jobs!(runner) authenticate_list_runners_jobs!(runner)
jobs = ::Ci::RunnerJobsFinder.new(runner, params).execute jobs = ::Ci::RunnerJobsFinder.new(runner, current_user, params).execute
present paginate(jobs), with: Entities::Ci::JobBasicWithProject present paginate(jobs), with: Entities::Ci::JobBasicWithProject
end end

View File

@ -82,8 +82,14 @@ module API
params.delete(:label_id) params.delete(:label_id)
params.delete(:name) params.delete(:name)
label = ::Labels::UpdateService.new(declared_params(include_missing: false)).execute(label) update_params = declared_params(include_missing: false)
render_validation_error!(label) unless label.valid?
if update_params.present?
authorize! :admin_label, label
label = ::Labels::UpdateService.new(update_params).execute(label)
render_validation_error!(label) unless label.valid?
end
if parent.is_a?(Project) && update_priority if parent.is_a?(Project) && update_priority
if priority.nil? if priority.nil?
@ -97,10 +103,10 @@ module API
end end
def delete_label(parent) def delete_label(parent)
authorize! :admin_label, parent
label = find_label(parent, params_id_or_title, include_ancestor_groups: false) label = find_label(parent, params_id_or_title, include_ancestor_groups: false)
authorize! :admin_label, label
destroy_conditionally!(label) destroy_conditionally!(label)
end end

View File

@ -10,20 +10,8 @@ module BulkImports
<<-'GRAPHQL' <<-'GRAPHQL'
query($full_path: ID!) { query($full_path: ID!) {
project(fullPath: $full_path) { project(fullPath: $full_path) {
description
visibility visibility
archived
created_at: createdAt created_at: createdAt
shared_runners_enabled: sharedRunnersEnabled
container_registry_enabled: containerRegistryEnabled
only_allow_merge_if_pipeline_succeeds: onlyAllowMergeIfPipelineSucceeds
only_allow_merge_if_all_discussions_are_resolved: onlyAllowMergeIfAllDiscussionsAreResolved
request_access_enabled: requestAccessEnabled
printing_merge_request_link_enabled: printingMergeRequestLinkEnabled
remove_source_branch_after_merge: removeSourceBranchAfterMerge
autoclose_referenced_issues: autocloseReferencedIssues
suggestion_commit_message: suggestionCommitMessage
wiki_enabled: wikiEnabled
} }
} }
GRAPHQL GRAPHQL

View File

@ -7,16 +7,18 @@ module BulkImports
PROJECT_IMPORT_TYPE = 'gitlab_project_migration' PROJECT_IMPORT_TYPE = 'gitlab_project_migration'
def transform(context, data) def transform(context, data)
project = {}
entity = context.entity entity = context.entity
visibility = data.delete('visibility') visibility = data.delete('visibility')
data['name'] = entity.destination_name project[:name] = entity.destination_name
data['path'] = entity.destination_name.parameterize project[:path] = entity.destination_name.parameterize
data['import_type'] = PROJECT_IMPORT_TYPE project[:created_at] = data['created_at']
data['visibility_level'] = Gitlab::VisibilityLevel.string_options[visibility] if visibility.present? project[:import_type] = PROJECT_IMPORT_TYPE
data['namespace_id'] = Namespace.find_by_full_path(entity.destination_namespace)&.id if entity.destination_namespace.present? project[:visibility_level] = Gitlab::VisibilityLevel.string_options[visibility] if visibility.present?
project[:namespace_id] = Namespace.find_by_full_path(entity.destination_namespace)&.id if entity.destination_namespace.present?
data.transform_keys!(&:to_sym) project
end end
end end
end end

View File

@ -15,6 +15,7 @@ module ErrorTracking
stack_trace = parse_stack_trace(event) stack_trace = parse_stack_trace(event)
Gitlab::ErrorTracking::ErrorEvent.new( Gitlab::ErrorTracking::ErrorEvent.new(
project_id: event['projectID'],
issue_id: event['groupID'], issue_id: event['groupID'],
date_received: event['dateReceived'], date_received: event['dateReceived'],
stack_trace_entries: stack_trace stack_trace_entries: stack_trace

View File

@ -14,6 +14,7 @@ module Gitlab
HEADER_READ_TIMEOUT = 20 HEADER_READ_TIMEOUT = 20
# rubocop: disable Style/RedundantBegin
# rubocop: disable Style/RedundantReturn # rubocop: disable Style/RedundantReturn
# rubocop: disable Cop/LineBreakAfterGuardClauses # rubocop: disable Cop/LineBreakAfterGuardClauses
# rubocop: disable Layout/EmptyLineAfterGuardClause # rubocop: disable Layout/EmptyLineAfterGuardClause
@ -21,9 +22,7 @@ module Gitlab
# Original method: # Original method:
# https://github.com/ruby/ruby/blob/cdb7d699d0641e8f081d590d06d07887ac09961f/lib/net/protocol.rb#L190-L200 # https://github.com/ruby/ruby/blob/cdb7d699d0641e8f081d590d06d07887ac09961f/lib/net/protocol.rb#L190-L200
override :readuntil override :readuntil
def readuntil(terminator, ignore_eof = false) def readuntil(terminator, ignore_eof = false, start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC))
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
begin begin
until idx = @rbuf.index(terminator) until idx = @rbuf.index(terminator)
if (elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) > HEADER_READ_TIMEOUT if (elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) > HEADER_READ_TIMEOUT
@ -39,6 +38,7 @@ module Gitlab
return rbuf_consume(@rbuf.size) return rbuf_consume(@rbuf.size)
end end
end end
# rubocop: disable Style/RedundantBegin
# rubocop: enable Style/RedundantReturn # rubocop: enable Style/RedundantReturn
# rubocop: enable Cop/LineBreakAfterGuardClauses # rubocop: enable Cop/LineBreakAfterGuardClauses
# rubocop: enable Layout/EmptyLineAfterGuardClause # rubocop: enable Layout/EmptyLineAfterGuardClause

View File

@ -13,7 +13,7 @@ module Gitlab
}.freeze }.freeze
def check_runner_upgrade_status(runner_version) def check_runner_upgrade_status(runner_version)
runner_version = ::Gitlab::VersionInfo.parse(runner_version) unless runner_version.is_a?(::Gitlab::VersionInfo) runner_version = ::Gitlab::VersionInfo.parse(runner_version, parse_suffix: true)
return :invalid unless runner_version.valid? return :invalid unless runner_version.valid?

View File

@ -38,7 +38,8 @@ module Gitlab
trace_size_exceeded: 'log size limit exceeded', trace_size_exceeded: 'log size limit exceeded',
builds_disabled: 'project builds are disabled', builds_disabled: 'project builds are disabled',
environment_creation_failure: 'environment creation failure', environment_creation_failure: 'environment creation failure',
deployment_rejected: 'deployment rejected' deployment_rejected: 'deployment rejected',
ip_restriction_failure: 'IP address restriction failure'
}.freeze }.freeze
private_constant :REASONS private_constant :REASONS

View File

@ -7,7 +7,7 @@ module Gitlab
class ErrorEvent class ErrorEvent
include ActiveModel::Model include ActiveModel::Model
attr_accessor :issue_id, :date_received, :stack_trace_entries, :gitlab_project attr_accessor :issue_id, :date_received, :stack_trace_entries, :gitlab_project, :project_id
def self.declarative_policy_class def self.declarative_policy_class
'ErrorTracking::BasePolicy' 'ErrorTracking::BasePolicy'

View File

@ -0,0 +1,50 @@
# frozen_string_literal: true
module Gitlab
module GithubImport
module Importer
module Events
class Closed
attr_reader :project, :user_id
def initialize(project, user_id)
@project = project
@user_id = user_id
end
# issue_event - An instance of `Gitlab::GithubImport::Representation::IssueEvent`.
def execute(issue_event)
create_event(issue_event)
create_state_event(issue_event)
end
private
def create_event(issue_event)
Event.create!(
project_id: project.id,
author_id: user_id,
action: 'closed',
target_type: Issue.name,
target_id: issue_event.issue_db_id,
created_at: issue_event.created_at,
updated_at: issue_event.created_at
)
end
def create_state_event(issue_event)
ResourceStateEvent.create!(
user_id: user_id,
issue_id: issue_event.issue_db_id,
source_commit: issue_event.commit_id,
state: ResourceStateEvent.states[:closed],
close_after_error_tracking_resolve: false,
close_auto_resolve_prometheus_alert: false,
created_at: issue_event.created_at
)
end
end
end
end
end
end

View File

@ -0,0 +1,41 @@
# frozen_string_literal: true
module Gitlab
module GithubImport
module Importer
class IssueEventImporter
attr_reader :issue_event, :project, :client, :user_finder
# issue_event - An instance of `Gitlab::GithubImport::Representation::IssueEvent`.
# project - An instance of `Project`.
# client - An instance of `Gitlab::GithubImport::Client`.
def initialize(issue_event, project, client)
@issue_event = issue_event
@project = project
@client = client
@user_finder = UserFinder.new(project, client)
end
def execute
case issue_event.event
when 'closed'
Gitlab::GithubImport::Importer::Events::Closed.new(project, author_id)
.execute(issue_event)
else
Gitlab::GithubImport::Logger.debug(
message: 'UNSUPPORTED_EVENT_TYPE',
event_type: issue_event.event, event_github_id: issue_event.id
)
end
end
private
def author_id
id, _status = user_finder.author_id_for(issue_event, author_key: :actor)
id
end
end
end
end
end

View File

@ -37,15 +37,15 @@ module Gitlab
private private
def noteables def parent_collection
project.merge_requests.where.not(iid: already_imported_noteables) # rubocop: disable CodeReuse/ActiveRecord project.merge_requests.where.not(iid: already_imported_parents) # rubocop: disable CodeReuse/ActiveRecord
end end
def page_counter_id(merge_request) def page_counter_id(merge_request)
"merge_request/#{merge_request.id}/#{collection_method}" "merge_request/#{merge_request.id}/#{collection_method}"
end end
def notes_imported_cache_key def parent_imported_cache_key
"github-importer/merge_request/diff_notes/already-imported/#{project.id}" "github-importer/merge_request/diff_notes/already-imported/#{project.id}"
end end
end end

View File

@ -0,0 +1,74 @@
# frozen_string_literal: true
module Gitlab
module GithubImport
module Importer
class SingleEndpointIssueEventsImporter
include ParallelScheduling
include SingleEndpointNotesImporting
PROCESSED_PAGE_CACHE_KEY = 'issues/%{issue_iid}/%{collection}'
BATCH_SIZE = 100
def initialize(project, client, parallel: true)
@project = project
@client = client
@parallel = parallel
@already_imported_cache_key = ALREADY_IMPORTED_CACHE_KEY %
{ project: project.id, collection: collection_method }
end
def each_associated(parent_record, associated)
return if already_imported?(associated)
Gitlab::GithubImport::ObjectCounter.increment(project, object_type, :fetched)
associated.issue_db_id = parent_record.id
yield(associated)
mark_as_imported(associated)
end
def importer_class
IssueEventImporter
end
def representation_class
Representation::IssueEvent
end
def sidekiq_worker_class
ImportIssueEventWorker
end
def object_type
:issue_event
end
def collection_method
:issue_timeline
end
def parent_collection
project.issues.where.not(iid: already_imported_parents).select(:id, :iid) # rubocop: disable CodeReuse/ActiveRecord
end
def parent_imported_cache_key
"github-importer/issues/#{collection_method}/already-imported/#{project.id}"
end
def page_counter_id(issue)
PROCESSED_PAGE_CACHE_KEY % { issue_iid: issue.iid, collection: collection_method }
end
def id_for_already_imported_cache(event)
event.id
end
def collection_options
{ state: 'all', sort: 'created', direction: 'asc' }
end
end
end
end
end

View File

@ -37,15 +37,15 @@ module Gitlab
private private
def noteables def parent_collection
project.issues.where.not(iid: already_imported_noteables) # rubocop: disable CodeReuse/ActiveRecord project.issues.where.not(iid: already_imported_parents) # rubocop: disable CodeReuse/ActiveRecord
end end
def page_counter_id(issue) def page_counter_id(issue)
"issue/#{issue.id}/#{collection_method}" "issue/#{issue.id}/#{collection_method}"
end end
def notes_imported_cache_key def parent_imported_cache_key
"github-importer/issue/notes/already-imported/#{project.id}" "github-importer/issue/notes/already-imported/#{project.id}"
end end
end end

View File

@ -37,15 +37,15 @@ module Gitlab
private private
def noteables def parent_collection
project.merge_requests.where.not(iid: already_imported_noteables) # rubocop: disable CodeReuse/ActiveRecord project.merge_requests.where.not(iid: already_imported_parents) # rubocop: disable CodeReuse/ActiveRecord
end end
def page_counter_id(merge_request) def page_counter_id(merge_request)
"merge_request/#{merge_request.id}/#{collection_method}" "merge_request/#{merge_request.id}/#{collection_method}"
end end
def notes_imported_cache_key def parent_imported_cache_key
"github-importer/merge_request/notes/already-imported/#{project.id}" "github-importer/merge_request/notes/already-imported/#{project.id}"
end end
end end

View File

@ -0,0 +1,49 @@
# frozen_string_literal: true
module Gitlab
module GithubImport
module Representation
class IssueEvent
include ToHash
include ExposeAttribute
attr_reader :attributes
expose_attribute :id, :actor, :event, :commit_id, :created_at
expose_attribute :issue_db_id # set in SingleEndpointIssueEventsImporter#each_associated
# Builds a event from a GitHub API response.
#
# event - An instance of `Sawyer::Resource` containing the event details.
def self.from_api_response(event)
new(
id: event.id,
actor: event.actor && Representation::User.from_api_response(event.actor),
event: event.event,
commit_id: event.commit_id,
issue_db_id: event.issue_db_id,
created_at: event.created_at
)
end
# Builds a event using a Hash that was built from a JSON payload.
def self.from_json_hash(raw_hash)
hash = Representation.symbolize_hash(raw_hash)
hash[:actor] = Representation::User.from_json_hash(hash[:actor]) if hash[:actor]
new(hash)
end
# attributes - A Hash containing the event details. The keys of this
# Hash (and any nested hashes) must be symbols.
def initialize(attributes)
@attributes = attributes
end
def github_identifiers
{ id: id }
end
end
end
end
end

View File

@ -4,9 +4,14 @@
# - SingleEndpointDiffNotesImporter # - SingleEndpointDiffNotesImporter
# - SingleEndpointIssueNotesImporter # - SingleEndpointIssueNotesImporter
# - SingleEndpointMergeRequestNotesImporter # - SingleEndpointMergeRequestNotesImporter
# if `github_importer_single_endpoint_notes_import` feature flag is on.
# #
# `github_importer_single_endpoint_notes_import` # - SingleEndpointIssueEventsImporter
# feature flag is on. # if `github_importer_issue_events_import` feature flag is on.
#
# Fetches associated objects page by page to each item of parent collection.
# Currently `associated` is note or event.
# Currently `parent` is MergeRequest or Issue record.
# #
# It fetches 1 PR's associated objects at a time using `issue_comments` or # It fetches 1 PR's associated objects at a time using `issue_comments` or
# `pull_request_comments` endpoint, which is slower than `NotesImporter` # `pull_request_comments` endpoint, which is slower than `NotesImporter`
@ -18,67 +23,75 @@ module Gitlab
module SingleEndpointNotesImporting module SingleEndpointNotesImporting
BATCH_SIZE = 100 BATCH_SIZE = 100
def each_object_to_import def each_object_to_import(&block)
each_notes_page do |page| each_associated_page do |parent_record, associated_page|
page.objects.each do |note| associated_page.objects.each do |associated|
next if already_imported?(note) each_associated(parent_record, associated, &block)
Gitlab::GithubImport::ObjectCounter.increment(project, object_type, :fetched)
yield(note)
mark_as_imported(note)
end end
end end
end end
def id_for_already_imported_cache(note) def id_for_already_imported_cache(associated)
note.id associated.id
end
def parent_collection
raise NotImplementedError
end
def parent_imported_cache_key
raise NotImplementedError
end
def page_counter_id(parent)
raise NotImplementedError
end end
private private
def each_notes_page # Sometimes we need to add some extra info from parent
noteables.each_batch(of: BATCH_SIZE, column: :iid) do |batch| # to associated record that is not available by default
batch.each do |noteable| # in Github API response object. For example:
# The page counter needs to be scoped by noteable to avoid skipping # lib/gitlab/github_import/importer/single_endpoint_issue_events_importer.rb:26
# pages of notes from already imported noteables. def each_associated(_parent_record, associated)
page_counter = PageCounter.new(project, page_counter_id(noteable)) return if already_imported?(associated)
Gitlab::GithubImport::ObjectCounter.increment(project, object_type, :fetched)
yield(associated)
mark_as_imported(associated)
end
def each_associated_page
parent_collection.each_batch(of: BATCH_SIZE, column: :iid) do |batch|
batch.each do |parent_record|
# The page counter needs to be scoped by parent_record to avoid skipping
# pages of notes from already imported parent_record.
page_counter = PageCounter.new(project, page_counter_id(parent_record))
repo = project.import_source repo = project.import_source
options = collection_options.merge(page: page_counter.current) options = collection_options.merge(page: page_counter.current)
client.each_page(collection_method, repo, noteable.iid, options) do |page| client.each_page(collection_method, repo, parent_record.iid, options) do |page|
next unless page_counter.set(page.number) next unless page_counter.set(page.number)
yield page yield parent_record, page
end end
mark_notes_imported(noteable) mark_parent_imported(parent_record)
end end
end end
end end
def mark_notes_imported(noteable) def mark_parent_imported(parent)
Gitlab::Cache::Import::Caching.set_add( Gitlab::Cache::Import::Caching.set_add(
notes_imported_cache_key, parent_imported_cache_key,
noteable.iid parent.iid
) )
end end
def already_imported_noteables def already_imported_parents
Gitlab::Cache::Import::Caching.values_from_set(notes_imported_cache_key) Gitlab::Cache::Import::Caching.values_from_set(parent_imported_cache_key)
end
def noteables
NotImplementedError
end
def notes_imported_cache_key
NotImplementedError
end
def page_counter_id(noteable)
NotImplementedError
end end
end end
end end

View File

@ -39,13 +39,9 @@ module Gitlab
# #
# If the object has no author ID we'll use the ID of the GitLab ghost # If the object has no author ID we'll use the ID of the GitLab ghost
# user. # user.
def author_id_for(object) def author_id_for(object, author_key: :author)
id = user_info = author_key == :actor ? object&.actor : object&.author
if object&.author id = user_info ? user_id_for(user_info) : GithubImport.ghost_user_id
user_id_for(object.author)
else
GithubImport.ghost_user_id
end
if id if id
[id, true] [id, true]

View File

@ -8,6 +8,8 @@ module Gitlab
DEFAULT_MAX_BYTES = 10.gigabytes.freeze DEFAULT_MAX_BYTES = 10.gigabytes.freeze
TIMEOUT_LIMIT = 210.seconds TIMEOUT_LIMIT = 210.seconds
ServiceError = Class.new(StandardError)
def initialize(archive_path:, max_bytes: self.class.max_bytes) def initialize(archive_path:, max_bytes: self.class.max_bytes)
@archive_path = archive_path @archive_path = archive_path
@max_bytes = max_bytes @max_bytes = max_bytes
@ -29,6 +31,8 @@ module Gitlab
pgrp = nil pgrp = nil
valid_archive = true valid_archive = true
validate_archive_path
Timeout.timeout(TIMEOUT_LIMIT) do Timeout.timeout(TIMEOUT_LIMIT) do
stdin, stdout, stderr, wait_thr = Open3.popen3(command, pgroup: true) stdin, stdout, stderr, wait_thr = Open3.popen3(command, pgroup: true)
stdin.close stdin.close
@ -78,15 +82,29 @@ module Gitlab
false false
end end
def validate_archive_path
Gitlab::Utils.check_path_traversal!(@archive_path)
raise(ServiceError, 'Archive path is not a string') unless @archive_path.is_a?(String)
raise(ServiceError, 'Archive path is a symlink') if File.lstat(@archive_path).symlink?
raise(ServiceError, 'Archive path is not a file') unless File.file?(@archive_path)
end
def command def command
"gzip -dc #{@archive_path} | wc -c" "gzip -dc #{@archive_path} | wc -c"
end end
def log_error(error) def log_error(error)
archive_size = begin
File.size(@archive_path)
rescue StandardError
nil
end
Gitlab::Import::Logger.info( Gitlab::Import::Logger.info(
message: error, message: error,
import_upload_archive_path: @archive_path, import_upload_archive_path: @archive_path,
import_upload_archive_size: File.size(@archive_path) import_upload_archive_size: archive_size
) )
end end
end end

View File

@ -38,7 +38,8 @@ module Gitlab
# @param [String] namespace # @param [String] namespace
def self.restore_full_path(namespace:, project:) def self.restore_full_path(namespace:, project:)
if project.include?(ENCODED_SLASH) if project.include?(ENCODED_SLASH)
project.gsub(ENCODED_SLASH, SLASH) # Replace multiple slashes with single ones to make sure the redirect stays on the same host
project.gsub(ENCODED_SLASH, SLASH).gsub(%r{\/{2,}}, '/')
else else
"#{namespace}/#{project}" "#{namespace}/#{project}"
end end

View File

@ -6,20 +6,27 @@ module Gitlab
attr_reader :major, :minor, :patch attr_reader :major, :minor, :patch
def self.parse(str) VERSION_REGEX = /(\d+)\.(\d+)\.(\d+)/.freeze
if str && m = str.match(/(\d+)\.(\d+)\.(\d+)/)
VersionInfo.new(m[1].to_i, m[2].to_i, m[3].to_i) def self.parse(str, parse_suffix: false)
if str.is_a?(self.class)
str
elsif str && m = str.match(VERSION_REGEX)
VersionInfo.new(m[1].to_i, m[2].to_i, m[3].to_i, parse_suffix ? m.post_match : nil)
else else
VersionInfo.new VersionInfo.new
end end
end end
def initialize(major = 0, minor = 0, patch = 0) def initialize(major = 0, minor = 0, patch = 0, suffix = nil)
@major = major @major = major
@minor = minor @minor = minor
@patch = patch @patch = patch
@suffix_s = suffix.to_s
end end
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def <=>(other) def <=>(other)
return unless other.is_a? VersionInfo return unless other.is_a? VersionInfo
return unless valid? && other.valid? return unless valid? && other.valid?
@ -36,19 +43,31 @@ module Gitlab
1 1
elsif @patch < other.patch elsif @patch < other.patch
-1 -1
elsif @suffix_s.empty? && other.suffix.present?
1
elsif other.suffix.empty? && @suffix_s.present?
-1
else else
0 suffix <=> other.suffix
end end
end end
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
def to_s def to_s
if valid? if valid?
"%d.%d.%d" % [@major, @minor, @patch] "%d.%d.%d%s" % [@major, @minor, @patch, @suffix_s]
else else
"Unknown" 'Unknown'
end end
end end
def suffix
@suffix ||= @suffix_s.strip.gsub('-', '.pre.').scan(/\d+|[a-z]+/i).map do |s|
/^\d+$/ =~ s ? s.to_i : s
end.freeze
end
def valid? def valid?
@major >= 0 && @minor >= 0 && @patch >= 0 && @major + @minor + @patch > 0 @major >= 0 && @minor >= 0 && @patch >= 0 && @major + @minor + @patch > 0
end end

View File

@ -21342,6 +21342,9 @@ msgstr ""
msgid "Invited" msgid "Invited"
msgstr "" msgstr ""
msgid "Invited group allowed email domains must contain a subset of the allowed email domains of the root ancestor group. Go to the group's 'Settings &gt; General' page and check 'Restrict membership by email domain'."
msgstr ""
msgid "IrkerService|Channels and users separated by whitespaces. %{recipients_docs_link}" msgid "IrkerService|Channels and users separated by whitespaces. %{recipients_docs_link}"
msgstr "" msgstr ""

View File

@ -52,8 +52,8 @@
"@babel/preset-env": "^7.18.2", "@babel/preset-env": "^7.18.2",
"@gitlab/at.js": "1.5.7", "@gitlab/at.js": "1.5.7",
"@gitlab/favicon-overlay": "2.0.0", "@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "2.22.0", "@gitlab/svgs": "2.24.0",
"@gitlab/ui": "42.11.0", "@gitlab/ui": "42.12.0",
"@gitlab/visual-review-tools": "1.7.3", "@gitlab/visual-review-tools": "1.7.3",
"@rails/actioncable": "6.1.4-7", "@rails/actioncable": "6.1.4-7",
"@rails/ujs": "6.1.4-7", "@rails/ujs": "6.1.4-7",

View File

@ -307,6 +307,18 @@ RSpec.describe Projects::ErrorTrackingController do
end end
describe 'format json' do describe 'format json' do
context 'when user is a reporter' do
before do
project.add_reporter(user)
end
it 'returns 404 error' do
update_issue
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when update result is successful' do context 'when update result is successful' do
before do before do
allow(issue_update_service).to receive(:execute) allow(issue_update_service).to receive(:execute)

View File

@ -70,6 +70,7 @@ RSpec.describe 'Database schema' do
oauth_applications: %w[owner_id], oauth_applications: %w[owner_id],
product_analytics_events_experimental: %w[event_id txn_id user_id], product_analytics_events_experimental: %w[event_id txn_id user_id],
project_build_artifacts_size_refreshes: %w[last_job_artifact_id], project_build_artifacts_size_refreshes: %w[last_job_artifact_id],
project_error_tracking_settings: %w[sentry_project_id],
project_group_links: %w[group_id], project_group_links: %w[group_id],
project_statistics: %w[namespace_id], project_statistics: %w[namespace_id],
projects: %w[creator_id ci_id mirror_user_id], projects: %w[creator_id ci_id mirror_user_id],

View File

@ -13,7 +13,7 @@ FactoryBot.define do
message { 'message' } message { 'message' }
culprit { 'culprit' } culprit { 'culprit' }
external_url { 'http://example.com/id' } external_url { 'http://example.com/id' }
project_id { 'project1' } project_id { '111111' }
project_name { 'project name' } project_name { 'project name' }
project_slug { 'project_name' } project_slug { 'project_name' }
short_id { 'ID' } short_id { 'ID' }

View File

@ -9,6 +9,7 @@ FactoryBot.define do
project_name { 'Sentry Project' } project_name { 'Sentry Project' }
organization_name { 'Sentry Org' } organization_name { 'Sentry Org' }
integrated { false } integrated { false }
sentry_project_id { 10 }
trait :disabled do trait :disabled do
enabled { false } enabled { false }

View File

@ -5,12 +5,17 @@ require 'spec_helper'
RSpec.describe Ci::RunnerJobsFinder do RSpec.describe Ci::RunnerJobsFinder do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:runner) { create(:ci_runner, :instance) } let(:runner) { create(:ci_runner, :instance) }
let(:user) { create(:user) }
let(:params) { {} }
subject { described_class.new(runner, params).execute } subject { described_class.new(runner, user, params).execute }
before do
project.add_developer(user)
end
describe '#execute' do describe '#execute' do
context 'when params is empty' do context 'when params is empty' do
let(:params) { {} }
let!(:job) { create(:ci_build, runner: runner, project: project) } let!(:job) { create(:ci_build, runner: runner, project: project) }
let!(:job1) { create(:ci_build, project: project) } let!(:job1) { create(:ci_build, project: project) }
@ -20,6 +25,50 @@ RSpec.describe Ci::RunnerJobsFinder do
end end
end end
context 'when the user has guest access' do
it 'does not returns jobs the user does not have permission to see' do
another_project = create(:project)
job = create(:ci_build, runner: runner, project: another_project)
another_project.add_guest(user)
is_expected.not_to match_array(job)
end
end
context 'when the user has permission to read all resources' do
let(:user) { create(:user, :admin) }
it 'returns all the jobs assigned to a runner' do
jobs = create_list(:ci_build, 5, runner: runner, project: project)
is_expected.to match_array(jobs)
end
end
context 'when the user has different access levels in different projects' do
it 'returns only the jobs the user has permission to see' do
guest_project = create(:project)
reporter_project = create(:project)
_guest_jobs = create_list(:ci_build, 2, runner: runner, project: guest_project)
reporter_jobs = create_list(:ci_build, 3, runner: runner, project: reporter_project)
guest_project.add_guest(user)
reporter_project.add_reporter(user)
is_expected.to match_array(reporter_jobs)
end
end
context 'when the user has reporter access level or greater' do
it 'returns jobs assigned to the Runner that the user has accesss to' do
jobs = create_list(:ci_build, 3, runner: runner, project: project)
is_expected.to match_array(jobs)
end
end
context 'when params contains status' do context 'when params contains status' do
Ci::HasStatus::AVAILABLE_STATUSES.each do |target_status| Ci::HasStatus::AVAILABLE_STATUSES.each do |target_status|
context "when status is #{target_status}" do context "when status is #{target_status}" do

View File

@ -2,22 +2,53 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe ::Packages::Conan::PackageFinder do RSpec.describe ::Packages::Conan::PackageFinder do
using RSpec::Parameterized::TableSyntax
let_it_be_with_reload(:project) { create(:project) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public) } let_it_be(:private_project) { create(:project, :private) }
let_it_be(:conan_package) { create(:conan_package, project: project) }
let_it_be(:conan_package2) { create(:conan_package, project: project) }
let_it_be(:errored_package) { create(:conan_package, :error, project: project) }
let_it_be(:private_package) { create(:conan_package, project: private_project) }
describe '#execute' do describe '#execute' do
let!(:conan_package) { create(:conan_package, project: project) } let(:query) { "#{conan_package.name.split('/').first[0, 3]}%" }
let!(:conan_package2) { create(:conan_package, project: project) } let(:finder) { described_class.new(user, query: query) }
subject { described_class.new(user, query: query).execute } subject { finder.execute }
context 'packages that are not installable' do where(:visibility, :role, :packages_visible) do
let!(:conan_package3) { create(:conan_package, :error, project: project) } :private | :maintainer | true
let!(:non_visible_project) { create(:project, :private) } :private | :developer | true
let!(:non_visible_conan_package) { create(:conan_package, project: non_visible_project) } :private | :reporter | true
let(:query) { "#{conan_package.name.split('/').first[0, 3]}%" } :private | :guest | false
:private | :anonymous | false
it { is_expected.to eq [conan_package, conan_package2] } :internal | :maintainer | true
:internal | :developer | true
:internal | :reporter | true
:internal | :guest | true
:internal | :anonymous | false
:public | :maintainer | true
:public | :developer | true
:public | :reporter | true
:public | :guest | true
:public | :anonymous | true
end
with_them do
let(:expected_packages) { packages_visible ? [conan_package, conan_package2] : [] }
let(:user) { role == :anonymous ? nil : super() }
before do
project.update_column(:visibility_level, Gitlab::VisibilityLevel.string_options[visibility.to_s])
project.add_user(user, role) unless role == :anonymous
end
it { is_expected.to eq(expected_packages) }
end end
end end
end end

View File

@ -53,7 +53,7 @@
}, },
"user": { "user": {
"allOf": [ "allOf": [
{ "$ref": "member_user.json" } { "$ref": "member_user_default.json" }
] ]
}, },
"state": { "type": "integer" }, "state": { "type": "integer" },

View File

@ -0,0 +1,35 @@
{
"type": "object",
"required": [
"id",
"name",
"username",
"created_at",
"last_activity_on",
"avatar_url",
"web_url",
"blocked",
"show_status"
],
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"username": { "type": "string" },
"created_at": { "type": ["string"] },
"avatar_url": { "type": ["string", "null"] },
"web_url": { "type": "string" },
"blocked": { "type": "boolean" },
"two_factor_enabled": { "type": "boolean" },
"availability": { "type": ["string", "null"] },
"last_activity_on": { "type": ["string", "null"] },
"status": {
"type": "object",
"required": ["emoji"],
"properties": {
"emoji": { "type": "string" }
},
"additionalProperties": false
},
"show_status": { "type": "boolean" }
}
}

View File

@ -130,7 +130,7 @@ describe('Markdown component', () => {
expect(columns[0].innerHTML).toContain('<img src="data:image/jpeg;base64'); expect(columns[0].innerHTML).toContain('<img src="data:image/jpeg;base64');
expect(columns[1].innerHTML).toContain('<img src="data:image/png;base64'); expect(columns[1].innerHTML).toContain('<img src="data:image/png;base64');
expect(columns[2].innerHTML).toContain('<img src="data:image/jpeg;base64'); expect(columns[2].innerHTML).toContain('<img src="data:image/jpeg;base64');
expect(columns[3].innerHTML).toContain('<img>'); expect(columns[3].innerHTML).toContain('<img src="attachment:bogus">');
expect(columns[4].innerHTML).toContain('<img src="https://www.google.com/'); expect(columns[4].innerHTML).toContain('<img src="https://www.google.com/');
}); });
}); });

View File

@ -159,4 +159,21 @@ describe('AccessDropdown', () => {
expect(template).not.toContain(user.name); expect(template).not.toContain(user.name);
}); });
}); });
describe('deployKeyRowHtml', () => {
const deployKey = {
id: 1,
title: 'title <script>alert(document.domain)</script>',
fullname: 'fullname <script>alert(document.domain)</script>',
avatar_url: '',
username: '',
};
it('escapes deploy key title and fullname', () => {
const template = dropdown.deployKeyRowHtml(deployKey);
expect(template).not.toContain(deployKey.title);
expect(template).not.toContain(deployKey.fullname);
});
});
}); });

View File

@ -1,57 +1,30 @@
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import { GlDropdown } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { DROPDOWN_VARIANT } from '~/vue_shared/components/color_select_dropdown/constants'; import { DROPDOWN_VARIANT } from '~/vue_shared/components/color_select_dropdown/constants';
import DropdownContents from '~/vue_shared/components/color_select_dropdown/dropdown_contents.vue'; import DropdownContents from '~/vue_shared/components/color_select_dropdown/dropdown_contents.vue';
import DropdownContentsColorView from '~/vue_shared/components/color_select_dropdown/dropdown_contents_color_view.vue'; import DropdownContentsColorView from '~/vue_shared/components/color_select_dropdown/dropdown_contents_color_view.vue';
import DropdownHeader from '~/vue_shared/components/color_select_dropdown/dropdown_header.vue';
import { color } from './mock_data'; import { color } from './mock_data';
const showDropdown = jest.fn();
const focusInput = jest.fn();
const defaultProps = { const defaultProps = {
dropdownTitle: '', dropdownTitle: '',
selectedColor: color, selectedColor: color,
dropdownButtonText: '', dropdownButtonText: 'Pick a color',
variant: '', variant: '',
isVisible: false, isVisible: false,
}; };
const GlDropdownStub = {
template: `
<div>
<slot name="header"></slot>
<slot></slot>
</div>
`,
methods: {
show: showDropdown,
hide: jest.fn(),
},
};
const DropdownHeaderStub = {
template: `
<div>Hello, I am a header</div>
`,
methods: {
focusInput,
},
};
describe('DropdownContent', () => { describe('DropdownContent', () => {
let wrapper; let wrapper;
const createComponent = ({ propsData = {} } = {}) => { const createComponent = ({ propsData = {} } = {}) => {
wrapper = shallowMount(DropdownContents, { wrapper = mountExtended(DropdownContents, {
propsData: { propsData: {
...defaultProps, ...defaultProps,
...propsData, ...propsData,
}, },
stubs: {
GlDropdown: GlDropdownStub,
DropdownHeader: DropdownHeaderStub,
},
}); });
}; };
@ -60,16 +33,17 @@ describe('DropdownContent', () => {
}); });
const findColorView = () => wrapper.findComponent(DropdownContentsColorView); const findColorView = () => wrapper.findComponent(DropdownContentsColorView);
const findDropdownHeader = () => wrapper.findComponent(DropdownHeaderStub); const findDropdownHeader = () => wrapper.findComponent(DropdownHeader);
const findDropdown = () => wrapper.findComponent(GlDropdownStub); const findDropdown = () => wrapper.findComponent(GlDropdown);
it('calls dropdown `show` method on `isVisible` prop change', async () => { it('calls dropdown `show` method on `isVisible` prop change', async () => {
createComponent(); createComponent();
const spy = jest.spyOn(wrapper.vm.$refs.dropdown, 'show');
await wrapper.setProps({ await wrapper.setProps({
isVisible: true, isVisible: true,
}); });
expect(showDropdown).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledTimes(1);
}); });
it('does not emit `setColor` event on dropdown hide if color did not change', () => { it('does not emit `setColor` event on dropdown hide if color did not change', () => {
@ -110,4 +84,12 @@ describe('DropdownContent', () => {
expect(findDropdownHeader().exists()).toBe(true); expect(findDropdownHeader().exists()).toBe(true);
}); });
it('handles no selected color', () => {
createComponent({ propsData: { selectedColor: {} } });
expect(wrapper.findByTestId('fallback-button-text').text()).toEqual(
defaultProps.dropdownButtonText,
);
});
}); });

View File

@ -7,24 +7,13 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
describe '#resolve' do describe '#resolve' do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, creator: user, namespace: user.namespace) } let_it_be(:project) { create(:project, :repository) }
let_it_be(:sha) { nil } let_it_be(:sha) { nil }
let_it_be(:content) do let_it_be(:content) do
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_includes.yml')) File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_includes.yml'))
end end
let(:ci_lint) do
ci_lint_double = instance_double(::Gitlab::Ci::Lint)
allow(ci_lint_double).to receive(:validate).and_return(fake_result)
ci_lint_double
end
before do
allow(::Gitlab::Ci::Lint).to receive(:new).and_return(ci_lint)
end
subject(:response) do subject(:response) do
resolve(described_class, resolve(described_class,
args: { project_path: project.full_path, content: content, sha: sha }, args: { project_path: project.full_path, content: content, sha: sha },
@ -51,52 +40,77 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
end end
end end
context 'with a valid .gitlab-ci.yml' do context 'when the user can create a pipeline' do
context 'with a sha' do
let(:sha) { '1231231' }
it_behaves_like 'a valid config file'
end
context 'without a sha' do
it_behaves_like 'a valid config file'
end
end
context 'with an invalid .gitlab-ci.yml' do
let(:content) { 'invalid' }
let(:fake_result) do
Gitlab::Ci::Lint::Result.new(
jobs: [],
merged_yaml: content,
errors: ['Invalid configuration format'],
warnings: [],
includes: []
)
end
it 'responds with errors about invalid syntax' do
expect(response[:status]).to eq(:invalid)
expect(response[:errors]).to eq(['Invalid configuration format'])
end
end
context 'with an invalid SHA' do
let_it_be(:sha) { ':' }
let(:ci_lint) do let(:ci_lint) do
ci_lint_double = instance_double(::Gitlab::Ci::Lint) ci_lint_double = instance_double(::Gitlab::Ci::Lint)
allow(ci_lint_double).to receive(:validate).and_raise(GRPC::InvalidArgument) allow(ci_lint_double).to receive(:validate).and_return(fake_result)
ci_lint_double ci_lint_double
end end
it 'logs the invalid SHA to Sentry' do before do
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_exception) allow(::Gitlab::Ci::Lint).to receive(:new).and_return(ci_lint)
.with(GRPC::InvalidArgument, sha: ':')
response project.add_developer(user)
end
context 'with a valid .gitlab-ci.yml' do
context 'with a sha' do
let(:sha) { '1231231' }
it_behaves_like 'a valid config file'
end
context 'without a sha' do
it_behaves_like 'a valid config file'
end
end
context 'with an invalid .gitlab-ci.yml' do
let(:content) { 'invalid' }
let(:fake_result) do
Gitlab::Ci::Lint::Result.new(
jobs: [],
merged_yaml: content,
errors: ['Invalid configuration format'],
warnings: [],
includes: []
)
end
it 'responds with errors about invalid syntax' do
expect(response[:status]).to eq(:invalid)
expect(response[:errors]).to match_array(['Invalid configuration format'])
end
end
context 'with an invalid SHA' do
let_it_be(:sha) { ':' }
let(:ci_lint) do
ci_lint_double = instance_double(::Gitlab::Ci::Lint)
allow(ci_lint_double).to receive(:validate).and_raise(GRPC::InvalidArgument)
ci_lint_double
end
it 'logs the invalid SHA to Sentry' do
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_exception)
.with(GRPC::InvalidArgument, sha: ':')
response
end
end
end
context 'when the user cannot create a pipeline' do
before do
project.add_guest(user)
end
it 'returns an error stating that the user cannot access the linting' do
expect(response).to be_instance_of(::Gitlab::Graphql::Errors::ResourceNotAvailable)
end end
end end
end end

View File

@ -150,19 +150,4 @@ RSpec.describe IntegrationsHelper do
end end
end end
end end
describe '#jira_issue_breadcrumb_link' do
let(:issue_reference) { nil }
subject { helper.jira_issue_breadcrumb_link(issue_reference) }
context 'when issue_reference contains HTML' do
let(:issue_reference) { "<script>alert('XSS')</script>" }
it 'escapes issue reference' do
is_expected.not_to include(issue_reference)
is_expected.to include(html_escape(issue_reference))
end
end
end
end end

View File

@ -52,6 +52,7 @@ RSpec.describe ProjectsHelper do
context 'api_url present' do context 'api_url present' do
let(:json) do let(:json) do
{ {
sentry_project_id: error_tracking_setting.sentry_project_id,
name: error_tracking_setting.project_name, name: error_tracking_setting.project_name,
organization_name: error_tracking_setting.organization_name, organization_name: error_tracking_setting.organization_name,
organization_slug: error_tracking_setting.organization_slug, organization_slug: error_tracking_setting.organization_slug,

View File

@ -38,4 +38,23 @@ RSpec.describe TimeboxesHelper do
end end
end end
end end
describe "#recent_releases_with_counts" do
let_it_be(:milestone) { create(:milestone) }
let_it_be(:project) { milestone.project }
let_it_be(:user) { create(:user) }
subject { helper.recent_releases_with_counts(milestone, user) }
before do
project.add_developer(user)
end
it "returns releases with counts" do
_old_releases = create_list(:release, 2, project: project, milestones: [milestone])
recent_public_releases = create_list(:release, 3, project: project, milestones: [milestone], released_at: '2022-01-01T18:00:00Z')
is_expected.to match([match_array(recent_public_releases), 5, 2])
end
end
end end

View File

@ -0,0 +1,79 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Net::HTTPResponse patch header read timeout' do
describe '.each_response_header' do
let(:server_response) do
<<~EOS
Content-Type: text/html
Header-Two: foo
Hello World
EOS
end
before do
stub_const('Gitlab::BufferedIo::HEADER_READ_TIMEOUT', 0.1)
end
subject(:each_response_header) { Net::HTTPResponse.each_response_header(socket) { |k, v| } }
context 'with Net::BufferedIO' do
let(:socket) { Net::BufferedIO.new(StringIO.new(server_response)) }
it 'does not forward start time to the socket' do
allow(socket).to receive(:readuntil).and_call_original
expect(socket).to receive(:readuntil).with("\n", true)
each_response_header
end
context 'when the response contains many consecutive spaces' do
before do
expect(socket).to receive(:readuntil).and_return(
"a: #{' ' * 100_000} b",
''
)
end
it 'has no regex backtracking issues' do
Timeout.timeout(1) do
each_response_header
end
end
end
end
context 'with Gitlab::BufferedIo' do
let(:mock_io) { StringIO.new(server_response) }
let(:socket) { Gitlab::BufferedIo.new(mock_io) }
it 'forwards start time to the socket' do
allow(socket).to receive(:readuntil).and_call_original
expect(socket).to receive(:readuntil).with("\n", true, kind_of(Numeric))
each_response_header
end
context 'when the response contains an infinite number of headers' do
before do
read_counter = 0
allow(mock_io).to receive(:read_nonblock) do
read_counter += 1
raise 'Test did not raise HeaderReadTimeout' if read_counter > 10
sleep 0.01
+"Yet-Another-Header: foo\n"
end
end
it 'raises a timeout error' do
expect { each_response_header }.to raise_error(Gitlab::HTTP::HeaderReadTimeout,
/Request timed out after reading headers for 0\.[0-9]+ seconds/)
end
end
end
end
end

View File

@ -25,18 +25,7 @@ RSpec.describe BulkImports::Projects::Pipelines::ProjectPipeline do
let(:project_data) do let(:project_data) do
{ {
'visibility' => 'private', 'visibility' => 'private',
'created_at' => 10.days.ago, 'created_at' => '2016-08-12T09:41:03'
'archived' => false,
'shared_runners_enabled' => true,
'container_registry_enabled' => true,
'only_allow_merge_if_pipeline_succeeds' => true,
'only_allow_merge_if_all_discussions_are_resolved' => true,
'request_access_enabled' => true,
'printing_merge_request_link_enabled' => true,
'remove_source_branch_after_merge' => true,
'autoclose_referenced_issues' => true,
'suggestion_commit_message' => 'message',
'wiki_enabled' => true
} }
end end
@ -58,17 +47,8 @@ RSpec.describe BulkImports::Projects::Pipelines::ProjectPipeline do
expect(imported_project).not_to be_nil expect(imported_project).not_to be_nil
expect(imported_project.group).to eq(group) expect(imported_project.group).to eq(group)
expect(imported_project.suggestion_commit_message).to eq('message') expect(imported_project.visibility).to eq(project_data['visibility'])
expect(imported_project.archived?).to eq(project_data['archived']) expect(imported_project.created_at).to eq(project_data['created_at'])
expect(imported_project.shared_runners_enabled?).to eq(project_data['shared_runners_enabled'])
expect(imported_project.container_registry_enabled?).to eq(project_data['container_registry_enabled'])
expect(imported_project.only_allow_merge_if_pipeline_succeeds?).to eq(project_data['only_allow_merge_if_pipeline_succeeds'])
expect(imported_project.only_allow_merge_if_all_discussions_are_resolved?).to eq(project_data['only_allow_merge_if_all_discussions_are_resolved'])
expect(imported_project.request_access_enabled?).to eq(project_data['request_access_enabled'])
expect(imported_project.printing_merge_request_link_enabled?).to eq(project_data['printing_merge_request_link_enabled'])
expect(imported_project.remove_source_branch_after_merge?).to eq(project_data['remove_source_branch_after_merge'])
expect(imported_project.autoclose_referenced_issues?).to eq(project_data['autoclose_referenced_issues'])
expect(imported_project.wiki_enabled?).to eq(project_data['wiki_enabled'])
end end
end end

View File

@ -25,8 +25,8 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer
let(:data) do let(:data) do
{ {
'name' => 'source_name', 'visibility' => 'private',
'visibility' => 'private' 'created_at' => '2016-11-18T09:29:42.634Z'
} }
end end
@ -76,8 +76,21 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer
end end
end end
it 'converts all keys to symbols' do context 'when data has extra keys' do
expect(transformed_data.keys).to contain_exactly(:name, :path, :import_type, :visibility_level, :namespace_id) it 'returns a fixed number of keys' do
data = {
'visibility' => 'private',
'created_at' => '2016-11-18T09:29:42.634Z',
'my_key' => 'my_key',
'another_key' => 'another_key',
'last_key' => 'last_key'
}
transformed_data = described_class.new.transform(context, data)
expect(transformed_data.keys)
.to contain_exactly(:created_at, :import_type, :name, :namespace_id, :path, :visibility_level)
end
end end
end end
end end

View File

@ -1,54 +1,50 @@
# rubocop:disable Style/FrozenStringLiteralComment # frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::BufferedIo do RSpec.describe Gitlab::BufferedIo do
describe '#readuntil' do describe '#readuntil' do
let(:never_ending_tcp_socket) do let(:mock_io) { StringIO.new('a') }
Class.new do let(:start_time) { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
def initialize(*_)
@read_counter = 0
end
def setsockopt(*_); end
def closed?
false
end
def close
true
end
def to_io
StringIO.new('Hello World!')
end
def write_nonblock(data, *_)
data.size
end
def read_nonblock(buffer_size, *_)
sleep 0.01
@read_counter += 1
raise 'Test did not raise HeaderReadTimeout' if @read_counter > 10
'H' * buffer_size
end
end
end
before do before do
stub_const('Gitlab::BufferedIo::HEADER_READ_TIMEOUT', 0.1) stub_const('Gitlab::BufferedIo::HEADER_READ_TIMEOUT', 0.1)
end end
subject(:readuntil) do subject(:readuntil) do
Gitlab::BufferedIo.new(never_ending_tcp_socket.new).readuntil('a') Gitlab::BufferedIo.new(mock_io).readuntil('a', false, start_time)
end end
it 'raises a timeout error' do it 'does not raise a timeout error' do
expect { readuntil }.to raise_error(Gitlab::HTTP::HeaderReadTimeout, /Request timed out after reading headers for 0\.[0-9]+ seconds/) expect { readuntil }.not_to raise_error
end
context 'when the response contains infinitely long headers' do
before do
read_counter = 0
allow(mock_io).to receive(:read_nonblock) do |buffer_size, *_|
read_counter += 1
raise 'Test did not raise HeaderReadTimeout' if read_counter > 10
sleep 0.01
'H' * buffer_size
end
end
it 'raises a timeout error' do
expect { readuntil }.to raise_error(Gitlab::HTTP::HeaderReadTimeout, /Request timed out after reading headers for 0\.[0-9]+ seconds/)
end
context 'when not passing start_time' do
subject(:readuntil) do
Gitlab::BufferedIo.new(mock_io).readuntil('a', false)
end
it 'raises a timeout error' do
expect { readuntil }.to raise_error(Gitlab::HTTP::HeaderReadTimeout, /Request timed out after reading headers for 0\.[0-9]+ seconds/)
end
end
end end
end end
end end
# rubocop:enable Style/FrozenStringLiteralComment

View File

@ -21,7 +21,9 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
end end
context 'with available_runner_releases configured up to 14.1.1' do context 'with available_runner_releases configured up to 14.1.1' do
let(:available_runner_releases) { %w[13.9.0 13.9.1 13.9.2 13.10.0 13.10.1 14.0.0 14.0.1 14.0.2 14.1.0 14.1.1] } let(:available_runner_releases) do
%w[13.9.0 13.9.1 13.9.2 13.10.0 13.10.1 14.0.0 14.0.1 14.0.2-rc1 14.0.2 14.1.0 14.1.1]
end
context 'with nil runner_version' do context 'with nil runner_version' do
let(:runner_version) { nil } let(:runner_version) { nil }
@ -62,10 +64,11 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
'v14.1.0/1.1.0' | :recommended # suffixes are correctly handled 'v14.1.0/1.1.0' | :recommended # suffixes are correctly handled
'v14.1.0' | :recommended # recommended since even though the GitLab instance is still on 14.0.x, there is a patch release (14.1.1) available which might contain security fixes 'v14.1.0' | :recommended # recommended since even though the GitLab instance is still on 14.0.x, there is a patch release (14.1.1) available which might contain security fixes
'v14.0.1' | :recommended # recommended upgrade since 14.0.2 is available 'v14.0.1' | :recommended # recommended upgrade since 14.0.2 is available
'v14.0.2-rc1' | :recommended # recommended upgrade since 14.0.2 is available and we'll move out of a release candidate
'v14.0.2' | :not_available # not available since 14.0.2 is the latest 14.0.x release available within the instance's major.minor version 'v14.0.2' | :not_available # not available since 14.0.2 is the latest 14.0.x release available within the instance's major.minor version
'v13.10.1' | :available # available upgrade: 14.1.1 'v13.10.1' | :available # available upgrade: 14.1.1
'v13.10.1~beta.1574.gf6ea9389' | :available # suffixes are correctly handled 'v13.10.1~beta.1574.gf6ea9389' | :recommended # suffixes are correctly handled, official 13.10.1 is available
'v13.10.1/1.1.0' | :available # suffixes are correctly handled 'v13.10.1/1.1.0' | :recommended # suffixes are correctly handled, official 13.10.1 is available
'v13.10.0' | :recommended # recommended upgrade since 13.10.1 is available 'v13.10.0' | :recommended # recommended upgrade since 13.10.1 is available
'v13.9.2' | :recommended # recommended upgrade since backports are no longer released for this version 'v13.9.2' | :recommended # recommended upgrade since backports are no longer released for this version
'v13.9.0' | :recommended # recommended upgrade since backports are no longer released for this version 'v13.9.0' | :recommended # recommended upgrade since backports are no longer released for this version

View File

@ -0,0 +1,72 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::Closed do
subject(:importer) { described_class.new(project, user.id) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:issue) { create(:issue, project: project) }
let(:commit_id) { nil }
let(:issue_event) do
Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
'id' => 6501124486,
'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
'actor' => { 'id' => 4, 'login' => 'alice' },
'event' => 'closed',
'created_at' => '2022-04-26 18:30:53 UTC',
'commit_id' => commit_id,
'issue_db_id' => issue.id
)
end
let(:expected_event_attrs) do
{
project_id: project.id,
author_id: user.id,
target_id: issue.id,
target_type: Issue.name,
action: 'closed',
created_at: issue_event.created_at,
updated_at: issue_event.created_at
}.stringify_keys
end
let(:expected_state_event_attrs) do
{
user_id: user.id,
issue_id: issue.id,
state: 'closed',
created_at: issue_event.created_at
}.stringify_keys
end
it 'creates expected event and state event' do
importer.execute(issue_event)
expect(issue.events.count).to eq 1
expect(issue.events[0].attributes)
.to include expected_event_attrs
expect(issue.resource_state_events.count).to eq 1
expect(issue.resource_state_events[0].attributes)
.to include expected_state_event_attrs
end
context 'when closed by commit' do
let!(:closing_commit) { create(:commit, project: project) }
let(:commit_id) { closing_commit.id }
it 'creates expected event and state event' do
importer.execute(issue_event)
expect(issue.events.count).to eq 1
state_event = issue.resource_state_events.last
expect(state_event.source_commit).to eq commit_id[0..40]
end
end
end

View File

@ -0,0 +1,77 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::IssueEventImporter, :clean_gitlab_redis_cache do
let(:importer) { described_class.new(issue_event, project, client) }
let(:project) { create(:project) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
let(:user) { create(:user) }
let(:issue) { create(:issue, project: project) }
let(:issue_event) do
Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
'id' => 6501124486,
'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
'actor' => { 'id' => actor_id, 'login' => 'alice' },
'event' => event_name,
'commit_id' => '570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
'commit_url' =>
'https://api.github.com/repos/octocat/Hello-World/commits/570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
'created_at' => '2022-04-26 18:30:53 UTC',
'performed_via_github_app' => nil
)
end
let(:actor_id) { user.id }
let(:event_name) { 'closed' }
shared_examples 'triggers specific event importer' do |importer_class|
it importer_class.name do
specific_importer = double(importer_class.name) # rubocop:disable RSpec/VerifiedDoubles
expect(importer_class)
.to receive(:new).with(project, user.id)
.and_return(specific_importer)
expect(specific_importer).to receive(:execute).with(issue_event)
importer.execute
end
end
describe '#execute' do
before do
allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
allow(finder).to receive(:author_id_for)
.with(issue_event, author_key: :actor)
.and_return(user.id, true)
end
issue_event.attributes[:issue_db_id] = issue.id
end
context "when it's closed issue event" do
let(:event_name) { 'closed' }
it_behaves_like 'triggers specific event importer',
Gitlab::GithubImport::Importer::Events::Closed
end
context "when it's unknown issue event" do
let(:event_name) { 'fake' }
it 'logs warning and skips' do
expect(Gitlab::GithubImport::Logger).to receive(:debug)
.with(
message: 'UNSUPPORTED_EVENT_TYPE',
event_type: issue_event.event,
event_github_id: issue_event.id
)
importer.execute
end
end
end
end

View File

@ -0,0 +1,128 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter do
let(:client) { double }
let_it_be(:project) { create(:project, :import_started, import_source: 'http://somegithub.com') }
let_it_be(:issue) { create(:issue, project: project) }
subject { described_class.new(project, client, parallel: parallel) }
let(:parallel) { true }
it { is_expected.to include_module(Gitlab::GithubImport::ParallelScheduling) }
describe '#importer_class' do
it { expect(subject.importer_class).to eq(Gitlab::GithubImport::Importer::IssueEventImporter) }
end
describe '#representation_class' do
it { expect(subject.representation_class).to eq(Gitlab::GithubImport::Representation::IssueEvent) }
end
describe '#sidekiq_worker_class' do
it { expect(subject.sidekiq_worker_class).to eq(Gitlab::GithubImport::ImportIssueEventWorker) }
end
describe '#object_type' do
it { expect(subject.object_type).to eq(:issue_event) }
end
describe '#collection_method' do
it { expect(subject.collection_method).to eq(:issue_timeline) }
end
describe '#page_counter_id' do
it { expect(subject.page_counter_id(issue)).to eq("issues/#{issue.iid}/issue_timeline") }
end
describe '#id_for_already_imported_cache' do
let(:event) { instance_double('Event', id: 1) }
it { expect(subject.id_for_already_imported_cache(event)).to eq(1) }
end
describe '#collection_options' do
it do
expect(subject.collection_options)
.to eq({ state: 'all', sort: 'created', direction: 'asc' })
end
end
describe '#each_object_to_import', :clean_gitlab_redis_cache do
let(:issue_event) do
struct = Struct.new(:id, :event, :created_at, :issue_db_id, keyword_init: true)
struct.new(id: rand(10), event: 'closed', created_at: '2022-04-26 18:30:53 UTC')
end
let(:page) do
instance_double(
Gitlab::GithubImport::Client::Page,
number: 1, objects: [issue_event]
)
end
let(:page_counter) { instance_double(Gitlab::GithubImport::PageCounter) }
before do
allow(client).to receive(:each_page)
.once
.with(
:issue_timeline,
project.import_source,
issue.iid,
{ state: 'all', sort: 'created', direction: 'asc', page: 1 }
).and_yield(page)
end
it 'imports each issue event page by page' do
counter = 0
subject.each_object_to_import do |object|
expect(object).to eq issue_event
expect(issue_event.issue_db_id).to eq issue.id
counter += 1
end
expect(counter).to eq 1
end
it 'triggers page number increment' do
expect(Gitlab::GithubImport::PageCounter)
.to receive(:new).with(project, 'issues/1/issue_timeline')
.and_return(page_counter)
expect(page_counter).to receive(:current).and_return(1)
expect(page_counter)
.to receive(:set).with(page.number).and_return(true)
counter = 0
subject.each_object_to_import { counter += 1 }
expect(counter).to eq 1
end
context 'when page is already processed' do
before do
page_counter = Gitlab::GithubImport::PageCounter.new(
project, subject.page_counter_id(issue)
)
page_counter.set(page.number)
end
it "doesn't process this page" do
counter = 0
subject.each_object_to_import { counter += 1 }
expect(counter).to eq 0
end
end
context 'when event is already processed' do
it "doesn't process this event" do
subject.mark_as_imported(issue_event)
counter = 0
subject.each_object_to_import { counter += 1 }
expect(counter).to eq 0
end
end
end
end

View File

@ -0,0 +1,111 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
shared_examples 'an IssueEvent' do
it 'returns an instance of IssueEvent' do
expect(issue_event).to be_an_instance_of(described_class)
end
context 'the returned IssueEvent' do
it 'includes the issue event id' do
expect(issue_event.id).to eq(6501124486)
end
it 'includes the issue event "event"' do
expect(issue_event.event).to eq('closed')
end
it 'includes the issue event commit_id' do
expect(issue_event.commit_id).to eq('570e7b2abdd848b95f2f578043fc23bd6f6fd24d')
end
it 'includes the issue_db_id' do
expect(issue_event.issue_db_id).to eq(100500)
end
context 'when actor data present' do
it 'includes the actor details' do
expect(issue_event.actor)
.to be_an_instance_of(Gitlab::GithubImport::Representation::User)
expect(issue_event.actor.id).to eq(4)
expect(issue_event.actor.login).to eq('alice')
end
end
context 'when actor data is empty' do
let(:with_actor) { false }
it 'does not return such info' do
expect(issue_event.actor).to eq nil
end
end
it 'includes the created timestamp' do
expect(issue_event.created_at).to eq('2022-04-26 18:30:53 UTC')
end
end
describe '#github_identifiers' do
it 'returns a hash with needed identifiers' do
expect(issue_event.github_identifiers).to eq({ id: 6501124486 })
end
end
end
describe '.from_api_response' do
let(:response) do
event_resource = Struct.new(
:id, :node_id, :url, :actor, :event, :commit_id, :commit_url,
:issue_db_id, :created_at, :performed_via_github_app,
keyword_init: true
)
user_resource = Struct.new(:id, :login, keyword_init: true)
event_resource.new(
id: 6501124486,
node_id: 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
url: 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
actor: with_actor ? user_resource.new(id: 4, login: 'alice') : nil,
event: 'closed',
commit_id: '570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
commit_url: 'https://api.github.com/repos/octocat/Hello-World/commits'\
'/570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
issue_db_id: 100500,
created_at: '2022-04-26 18:30:53 UTC',
performed_via_github_app: nil
)
end
let(:with_actor) { true }
it_behaves_like 'an IssueEvent' do
let(:issue_event) { described_class.from_api_response(response) }
end
end
describe '.from_json_hash' do
it_behaves_like 'an IssueEvent' do
let(:hash) do
{
'id' => 6501124486,
'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
'actor' => (with_actor ? { 'id' => 4, 'login' => 'alice' } : nil),
'event' => 'closed',
'commit_id' => '570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
'commit_url' =>
'https://api.github.com/repos/octocat/Hello-World/commits/570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
"issue_db_id" => 100500,
'created_at' => '2022-04-26 18:30:53 UTC',
'performed_via_github_app' => nil
}
end
let(:with_actor) { true }
let(:issue_event) { described_class.from_json_hash(hash) }
end
end
end

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::SingleEndpointNotesImporting do
let(:importer_class) do
Class.new do
def self.name
'MyImporter'
end
include(Gitlab::GithubImport::SingleEndpointNotesImporting)
end
end
let(:importer_instance) { importer_class.new }
describe '#parent_collection' do
it { expect { importer_instance.parent_collection }.to raise_error(NotImplementedError) }
end
describe '#parent_imported_cache_key' do
it { expect { importer_instance.parent_imported_cache_key }.to raise_error(NotImplementedError) }
end
describe '#page_counter_id' do
it { expect { importer_instance.page_counter_id(build(:merge_request)) }.to raise_error(NotImplementedError) }
end
end

View File

@ -420,6 +420,8 @@ project:
- zentao_integration - zentao_integration
# dingtalk_integration JiHu-specific, see https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/417 # dingtalk_integration JiHu-specific, see https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/417
- dingtalk_integration - dingtalk_integration
# dingtalk_integration JiHu-specific, see https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/640
- feishu_integration
- redmine_integration - redmine_integration
- youtrack_integration - youtrack_integration
- custom_issue_tracker_integration - custom_issue_tracker_integration

View File

@ -86,6 +86,65 @@ RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator do
include_examples 'logs raised exception and terminates validator process group' include_examples 'logs raised exception and terminates validator process group'
end end
end end
context 'archive path validation' do
let(:filesize) { nil }
before do
expect(Gitlab::Import::Logger)
.to receive(:info)
.with(
import_upload_archive_path: filepath,
import_upload_archive_size: filesize,
message: error_message
)
end
context 'when archive path is traversed' do
let(:filepath) { '/foo/../bar' }
let(:error_message) { 'Invalid path' }
it 'returns false' do
expect(subject.valid?).to eq(false)
end
end
context 'when archive path is not a string' do
let(:filepath) { 123 }
let(:error_message) { 'Archive path is not a string' }
it 'returns false' do
expect(subject.valid?).to eq(false)
end
end
context 'which archive path is a symlink' do
let(:filepath) { File.join(Dir.tmpdir, 'symlink') }
let(:error_message) { 'Archive path is a symlink' }
before do
FileUtils.ln_s(filepath, filepath, force: true)
end
it 'returns false' do
expect(subject.valid?).to eq(false)
end
end
context 'when archive path is not a file' do
let(:filepath) { Dir.mktmpdir }
let(:filesize) { File.size(filepath) }
let(:error_message) { 'Archive path is not a file' }
after do
FileUtils.rm_rf(filepath)
end
it 'returns false' do
expect(subject.valid?).to eq(false)
end
end
end
end end
def create_compressed_file def create_compressed_file

View File

@ -2,28 +2,44 @@
require 'fast_spec_helper' require 'fast_spec_helper'
RSpec.describe 'Gitlab::VersionInfo' do RSpec.describe Gitlab::VersionInfo do
before do before do
@unknown = Gitlab::VersionInfo.new @unknown = described_class.new
@v0_0_1 = Gitlab::VersionInfo.new(0, 0, 1) @v0_0_1 = described_class.new(0, 0, 1)
@v0_1_0 = Gitlab::VersionInfo.new(0, 1, 0) @v0_1_0 = described_class.new(0, 1, 0)
@v1_0_0 = Gitlab::VersionInfo.new(1, 0, 0) @v1_0_0 = described_class.new(1, 0, 0)
@v1_0_1 = Gitlab::VersionInfo.new(1, 0, 1) @v1_0_1 = described_class.new(1, 0, 1)
@v1_1_0 = Gitlab::VersionInfo.new(1, 1, 0) @v1_0_1_b1 = described_class.new(1, 0, 1, '-b1')
@v2_0_0 = Gitlab::VersionInfo.new(2, 0, 0) @v1_0_1_rc1 = described_class.new(1, 0, 1, '-rc1')
@v1_0_1_rc2 = described_class.new(1, 0, 1, '-rc2')
@v1_1_0 = described_class.new(1, 1, 0)
@v1_1_0_beta1 = described_class.new(1, 1, 0, '-beta1')
@v2_0_0 = described_class.new(2, 0, 0)
@v13_10_1_1574_89 = described_class.parse("v13.10.1~beta.1574.gf6ea9389", parse_suffix: true)
@v13_10_1_1575_89 = described_class.parse("v13.10.1~beta.1575.gf6ea9389", parse_suffix: true)
@v13_10_1_1575_90 = described_class.parse("v13.10.1~beta.1575.gf6ea9390", parse_suffix: true)
end end
describe '>' do describe '>' do
it { expect(@v2_0_0).to be > @v1_1_0 } it { expect(@v2_0_0).to be > @v1_1_0 }
it { expect(@v1_1_0).to be > @v1_0_1 } it { expect(@v1_1_0).to be > @v1_0_1 }
it { expect(@v1_0_1_b1).to be > @v1_0_0 }
it { expect(@v1_0_1_rc1).to be > @v1_0_0 }
it { expect(@v1_0_1_rc1).to be > @v1_0_1_b1 }
it { expect(@v1_0_1_rc2).to be > @v1_0_1_rc1 }
it { expect(@v1_0_1).to be > @v1_0_1_rc1 }
it { expect(@v1_0_1).to be > @v1_0_1_rc2 }
it { expect(@v1_0_1).to be > @v1_0_0 } it { expect(@v1_0_1).to be > @v1_0_0 }
it { expect(@v1_0_0).to be > @v0_1_0 } it { expect(@v1_0_0).to be > @v0_1_0 }
it { expect(@v1_1_0_beta1).to be > @v1_0_1_rc2 }
it { expect(@v1_1_0).to be > @v1_1_0_beta1 }
it { expect(@v0_1_0).to be > @v0_0_1 } it { expect(@v0_1_0).to be > @v0_0_1 }
end end
describe '>=' do describe '>=' do
it { expect(@v2_0_0).to be >= Gitlab::VersionInfo.new(2, 0, 0) } it { expect(@v2_0_0).to be >= described_class.new(2, 0, 0) }
it { expect(@v2_0_0).to be >= @v1_1_0 } it { expect(@v2_0_0).to be >= @v1_1_0 }
it { expect(@v1_0_1_rc2).to be >= @v1_0_1_rc1 }
end end
describe '<' do describe '<' do
@ -31,64 +47,115 @@ RSpec.describe 'Gitlab::VersionInfo' do
it { expect(@v0_1_0).to be < @v1_0_0 } it { expect(@v0_1_0).to be < @v1_0_0 }
it { expect(@v1_0_0).to be < @v1_0_1 } it { expect(@v1_0_0).to be < @v1_0_1 }
it { expect(@v1_0_1).to be < @v1_1_0 } it { expect(@v1_0_1).to be < @v1_1_0 }
it { expect(@v1_0_0).to be < @v1_0_1_rc2 }
it { expect(@v1_0_1_rc1).to be < @v1_0_1 }
it { expect(@v1_0_1_rc1).to be < @v1_0_1_rc2 }
it { expect(@v1_0_1_rc2).to be < @v1_0_1 }
it { expect(@v1_1_0).to be < @v2_0_0 } it { expect(@v1_1_0).to be < @v2_0_0 }
it { expect(@v13_10_1_1574_89).to be < @v13_10_1_1575_89 }
it { expect(@v13_10_1_1575_89).to be < @v13_10_1_1575_90 }
end end
describe '<=' do describe '<=' do
it { expect(@v0_0_1).to be <= Gitlab::VersionInfo.new(0, 0, 1) } it { expect(@v0_0_1).to be <= described_class.new(0, 0, 1) }
it { expect(@v0_0_1).to be <= @v0_1_0 } it { expect(@v0_0_1).to be <= @v0_1_0 }
it { expect(@v1_0_1_b1).to be <= @v1_0_1_rc1 }
it { expect(@v1_0_1_rc1).to be <= @v1_0_1_rc2 }
it { expect(@v1_1_0_beta1).to be <= @v1_1_0 }
end end
describe '==' do describe '==' do
it { expect(@v0_0_1).to eq(Gitlab::VersionInfo.new(0, 0, 1)) } it { expect(@v0_0_1).to eq(described_class.new(0, 0, 1)) }
it { expect(@v0_1_0).to eq(Gitlab::VersionInfo.new(0, 1, 0)) } it { expect(@v0_1_0).to eq(described_class.new(0, 1, 0)) }
it { expect(@v1_0_0).to eq(Gitlab::VersionInfo.new(1, 0, 0)) } it { expect(@v1_0_0).to eq(described_class.new(1, 0, 0)) }
it { expect(@v1_0_1_rc1).to eq(described_class.new(1, 0, 1, '-rc1')) }
end end
describe '!=' do describe '!=' do
it { expect(@v0_0_1).not_to eq(@v0_1_0) } it { expect(@v0_0_1).not_to eq(@v0_1_0) }
it { expect(@v1_0_1_rc1).not_to eq(@v1_0_1_rc2) }
end end
describe '.unknown' do describe '.unknown' do
it { expect(@unknown).not_to be @v0_0_1 } it { expect(@unknown).not_to be @v0_0_1 }
it { expect(@unknown).not_to be Gitlab::VersionInfo.new } it { expect(@unknown).not_to be described_class.new }
it { expect {@unknown > @v0_0_1}.to raise_error(ArgumentError) } it { expect {@unknown > @v0_0_1}.to raise_error(ArgumentError) }
it { expect {@unknown < @v0_0_1}.to raise_error(ArgumentError) } it { expect {@unknown < @v0_0_1}.to raise_error(ArgumentError) }
end end
describe '.parse' do describe '.parse' do
it { expect(Gitlab::VersionInfo.parse("1.0.0")).to eq(@v1_0_0) } it { expect(described_class.parse("1.0.0")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("1.0.0.1")).to eq(@v1_0_0) } it { expect(described_class.parse("1.0.0.1")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("1.0.0-ee")).to eq(@v1_0_0) } it { expect(described_class.parse("1.0.0-ee")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("1.0.0-rc1")).to eq(@v1_0_0) } it { expect(described_class.parse("1.0.0-rc1")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("1.0.0-rc1-ee")).to eq(@v1_0_0) } it { expect(described_class.parse("1.0.0-rc1-ee")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("git 1.0.0b1")).to eq(@v1_0_0) } it { expect(described_class.parse("git 1.0.0b1")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("git 1.0b1")).not_to be_valid } it { expect(described_class.parse("git 1.0b1")).not_to be_valid }
context 'with parse_suffix: true' do
let(:versions) do
<<-VERSIONS.lines
0.0.1
0.1.0
1.0.0
1.0.1-b1
1.0.1-rc1
1.0.1-rc2
1.0.1
1.1.0-beta1
1.1.0
2.0.0
v13.10.0-pre
v13.10.0-rc1
v13.10.0-rc2
v13.10.0
v13.10.1~beta.1574.gf6ea9389
v13.10.1~beta.1575.gf6ea9389
v13.10.1-rc1
v13.10.1-rc2
v13.10.1
VERSIONS
end
let(:parsed_versions) do
versions.map(&:strip).map { |version| described_class.parse(version, parse_suffix: true) }
end
it 'versions are returned in a correct order' do
expect(parsed_versions.shuffle.sort).to eq(parsed_versions)
end
end
end end
describe '.to_s' do describe '.to_s' do
it { expect(@v1_0_0.to_s).to eq("1.0.0") } it { expect(@v1_0_0.to_s).to eq("1.0.0") }
it { expect(@v1_0_1_rc1.to_s).to eq("1.0.1-rc1") }
it { expect(@unknown.to_s).to eq("Unknown") } it { expect(@unknown.to_s).to eq("Unknown") }
end end
describe '.hash' do describe '.hash' do
it { expect(Gitlab::VersionInfo.parse("1.0.0").hash).to eq(@v1_0_0.hash) } it { expect(described_class.parse("1.0.0").hash).to eq(@v1_0_0.hash) }
it { expect(Gitlab::VersionInfo.parse("1.0.0.1").hash).to eq(@v1_0_0.hash) } it { expect(described_class.parse("1.0.0.1").hash).to eq(@v1_0_0.hash) }
it { expect(Gitlab::VersionInfo.parse("1.0.1b1").hash).to eq(@v1_0_1.hash) } it { expect(described_class.parse("1.0.1b1").hash).to eq(@v1_0_1.hash) }
it { expect(described_class.parse("1.0.1-rc1", parse_suffix: true).hash).to eq(@v1_0_1_rc1.hash) }
end end
describe '.eql?' do describe '.eql?' do
it { expect(Gitlab::VersionInfo.parse("1.0.0").eql?(@v1_0_0)).to be_truthy } it { expect(described_class.parse("1.0.0").eql?(@v1_0_0)).to be_truthy }
it { expect(Gitlab::VersionInfo.parse("1.0.0.1").eql?(@v1_0_0)).to be_truthy } it { expect(described_class.parse("1.0.0.1").eql?(@v1_0_0)).to be_truthy }
it { expect(@v1_0_1_rc1.eql?(@v1_0_1_rc1)).to be_truthy }
it { expect(@v1_0_1_rc1.eql?(@v1_0_1_rc2)).to be_falsey }
it { expect(@v1_0_1_rc1.eql?(@v1_0_1)).to be_falsey }
it { expect(@v1_0_1.eql?(@v1_0_0)).to be_falsey } it { expect(@v1_0_1.eql?(@v1_0_0)).to be_falsey }
it { expect(@v1_1_0.eql?(@v1_0_0)).to be_falsey } it { expect(@v1_1_0.eql?(@v1_0_0)).to be_falsey }
it { expect(@v1_0_0.eql?(@v1_0_0)).to be_truthy } it { expect(@v1_0_0.eql?(@v1_0_0)).to be_truthy }
it { expect([@v1_0_0, @v1_1_0, @v1_0_0].uniq).to eq [@v1_0_0, @v1_1_0] } it { expect([@v1_0_0, @v1_1_0, @v1_0_0, @v1_0_1_rc1, @v1_0_1_rc1].uniq).to eq [@v1_0_0, @v1_1_0, @v1_0_1_rc1] }
end end
describe '.same_minor_version?' do describe '.same_minor_version?' do
it { expect(@v0_1_0.same_minor_version?(@v0_0_1)).to be_falsey } it { expect(@v0_1_0.same_minor_version?(@v0_0_1)).to be_falsey }
it { expect(@v1_0_1.same_minor_version?(@v1_0_0)).to be_truthy } it { expect(@v1_0_1.same_minor_version?(@v1_0_0)).to be_truthy }
it { expect(@v1_0_1_rc1.same_minor_version?(@v1_0_0)).to be_truthy }
it { expect(@v1_0_0.same_minor_version?(@v1_0_1)).to be_truthy } it { expect(@v1_0_0.same_minor_version?(@v1_0_1)).to be_truthy }
it { expect(@v1_1_0.same_minor_version?(@v1_0_0)).to be_falsey } it { expect(@v1_1_0.same_minor_version?(@v1_0_0)).to be_falsey }
it { expect(@v2_0_0.same_minor_version?(@v1_0_0)).to be_falsey } it { expect(@v2_0_0.same_minor_version?(@v1_0_0)).to be_falsey }
@ -98,5 +165,6 @@ RSpec.describe 'Gitlab::VersionInfo' do
it { expect(@v0_1_0.without_patch).to eq(@v0_1_0) } it { expect(@v0_1_0.without_patch).to eq(@v0_1_0) }
it { expect(@v1_0_0.without_patch).to eq(@v1_0_0) } it { expect(@v1_0_0.without_patch).to eq(@v1_0_0) }
it { expect(@v1_0_1.without_patch).to eq(@v1_0_0) } it { expect(@v1_0_1.without_patch).to eq(@v1_0_0) }
it { expect(@v1_0_1_rc1.without_patch).to eq(@v1_0_0) }
end end
end end

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