Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
5facc34f44
commit
676109e1b3
|
|
@ -234,7 +234,15 @@ danger-review:
|
|||
- run_timed_command "bundle install --jobs=$(nproc) --path=vendor --retry=3 --quiet --with danger"
|
||||
- run_timed_command "retry yarn install --frozen-lockfile"
|
||||
script:
|
||||
- run_timed_command "bundle exec danger --fail-on-errors=true --verbose"
|
||||
- >
|
||||
if [ -z "$DANGER_GITLAB_API_TOKEN" ]; then
|
||||
# Force danger to skip CI source GitLab and fallback to "local only git repo".
|
||||
unset GITLAB_CI
|
||||
# We need to base SHA to help danger determine the base commit for this shallow clone.
|
||||
run_timed_command "bundle exec danger dry_run --fail-on-errors=true --verbose --base='$CI_MERGE_REQUEST_DIFF_BASE_SHA'"
|
||||
else
|
||||
run_timed_command "bundle exec danger --fail-on-errors=true --verbose"
|
||||
fi
|
||||
|
||||
update-danger-review-cache:
|
||||
extends:
|
||||
|
|
|
|||
|
|
@ -1110,7 +1110,7 @@
|
|||
|
||||
.review:rules:danger:
|
||||
rules:
|
||||
- if: '$DANGER_GITLAB_API_TOKEN && $CI_MERGE_REQUEST_IID'
|
||||
- if: '$CI_MERGE_REQUEST_IID'
|
||||
|
||||
###############
|
||||
# Setup rules #
|
||||
|
|
|
|||
|
|
@ -927,13 +927,6 @@ Style/RedundantRegexpEscape:
|
|||
Style/RedundantSelf:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 2
|
||||
# Cop supports --auto-correct.
|
||||
Style/RedundantSelfAssignment:
|
||||
Exclude:
|
||||
- 'app/models/concerns/issuable.rb'
|
||||
- 'spec/db/schema_spec.rb'
|
||||
|
||||
# Offense count: 213
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, AllowInnerSlashes.
|
||||
|
|
|
|||
65
CHANGELOG.md
65
CHANGELOG.md
|
|
@ -2,6 +2,28 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 13.10.1 (2021-03-31)
|
||||
|
||||
### Security (6 changes)
|
||||
|
||||
- Leave pool repository on fork unlinking.
|
||||
- Fixed XSS in merge requests sidebar.
|
||||
- Fix arbitrary read/write in AsciiDoctor and Kroki gems.
|
||||
- Prevent infinite loop when checking if collaboration is allowed.
|
||||
- Disable arbitrary URI and file reads in JSON validator.
|
||||
- Require POST request to trigger system hooks.
|
||||
|
||||
### Removed (1 change)
|
||||
|
||||
- Make HipChat project service do nothing. !57434
|
||||
|
||||
### Other (3 changes)
|
||||
|
||||
- Remove direct mimemagic dependency. !57387
|
||||
- Refactor MimeMagic calls to new MimeType class. !57421
|
||||
- Switch to using a fake mimemagic gem. !57443
|
||||
|
||||
|
||||
## 13.10.0 (2021-03-22)
|
||||
|
||||
### Security (3 changes)
|
||||
|
|
@ -529,6 +551,28 @@ entry.
|
|||
- Convert mattermost alert to pajamas. !56556
|
||||
|
||||
|
||||
## 13.9.5 (2021-03-31)
|
||||
|
||||
### Security (6 changes)
|
||||
|
||||
- Leave pool repository on fork unlinking.
|
||||
- Fixed XSS in merge requests sidebar.
|
||||
- Fix arbitrary read/write in AsciiDoctor and Kroki gems.
|
||||
- Prevent infinite loop when checking if collaboration is allowed.
|
||||
- Disable arbitrary URI and file reads in JSON validator.
|
||||
- Require POST request to trigger system hooks.
|
||||
|
||||
### Removed (1 change)
|
||||
|
||||
- Make HipChat project service do nothing. !57434
|
||||
|
||||
### Other (3 changes)
|
||||
|
||||
- Remove direct mimemagic dependency. !57387
|
||||
- Refactor MimeMagic calls to new MimeType class. !57421
|
||||
- Switch to using a fake mimemagic gem. !57443
|
||||
|
||||
|
||||
## 13.9.4 (2021-03-17)
|
||||
|
||||
### Security (1 change)
|
||||
|
|
@ -1144,6 +1188,27 @@ entry.
|
|||
- Apply new GitLab UI for buttons in pipeline schedules.
|
||||
|
||||
|
||||
## 13.8.7 (2021-03-31)
|
||||
|
||||
### Security (5 changes)
|
||||
|
||||
- Fixed XSS in merge requests sidebar.
|
||||
- Leave pool repository on fork unlinking.
|
||||
- Fix arbitrary read/write in AsciiDoctor and Kroki gems.
|
||||
- Prevent infinite loop when checking if collaboration is allowed.
|
||||
- Require POST request to trigger system hooks.
|
||||
|
||||
### Removed (1 change)
|
||||
|
||||
- Make HipChat project service do nothing. !57434
|
||||
|
||||
### Other (3 changes)
|
||||
|
||||
- Remove direct mimemagic dependency. !57387
|
||||
- Refactor MimeMagic calls to new MimeType class. !57421
|
||||
- Switch to using a fake mimemagic gem. !57443
|
||||
|
||||
|
||||
## 13.8.6 (2021-03-17)
|
||||
|
||||
### Security (1 change)
|
||||
|
|
|
|||
2
Gemfile
2
Gemfile
|
|
@ -274,7 +274,7 @@ gem 'licensee', '~> 9.14.1'
|
|||
gem 'charlock_holmes', '~> 0.7.7'
|
||||
|
||||
# Detect mime content type from content
|
||||
gem 'ruby-magic-static', '~> 0.3.4'
|
||||
gem 'ruby-magic-static', '~> 0.3.5'
|
||||
|
||||
# Fake version of the gem to trick bundler
|
||||
gem 'mimemagic', '0.3.7', path: 'vendor/shims/mimemagic', require: false
|
||||
|
|
|
|||
|
|
@ -1113,7 +1113,8 @@ GEM
|
|||
i18n
|
||||
ruby-fogbugz (0.2.1)
|
||||
crack (~> 0.4)
|
||||
ruby-magic-static (0.3.4)
|
||||
ruby-magic-static (0.3.5)
|
||||
mini_portile2 (~> 2.5.0)
|
||||
ruby-prof (1.3.1)
|
||||
ruby-progressbar (1.11.0)
|
||||
ruby-saml (1.7.2)
|
||||
|
|
@ -1559,7 +1560,7 @@ DEPENDENCIES
|
|||
rspec_junit_formatter
|
||||
rspec_profiling (~> 0.0.6)
|
||||
ruby-fogbugz (~> 0.2.1)
|
||||
ruby-magic-static (~> 0.3.4)
|
||||
ruby-magic-static (~> 0.3.5)
|
||||
ruby-prof (~> 1.3.0)
|
||||
ruby-progressbar (~> 1.10)
|
||||
ruby_parser (~> 3.15)
|
||||
|
|
|
|||
|
|
@ -119,8 +119,8 @@ export default {
|
|||
<gl-button @click="onCancel">{{ s__('Cancel') }}</gl-button>
|
||||
<gl-button
|
||||
:disabled="!canSubmit"
|
||||
category="primary"
|
||||
variant="warning"
|
||||
category="secondary"
|
||||
variant="danger"
|
||||
@click="onSecondaryAction"
|
||||
>
|
||||
{{ secondaryAction }}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ class CommitStatus < ApplicationRecord
|
|||
scope :for_ref, -> (ref) { where(ref: ref) }
|
||||
scope :by_name, -> (name) { where(name: name) }
|
||||
scope :in_pipelines, ->(pipelines) { where(pipeline: pipelines) }
|
||||
scope :eager_load_pipeline, -> { eager_load(:pipeline, project: { namespace: :route }) }
|
||||
|
||||
scope :for_project_paths, -> (paths) do
|
||||
where(project: Project.where_full_path_in(Array(paths)))
|
||||
|
|
|
|||
|
|
@ -324,7 +324,7 @@ module Issuable
|
|||
# This prevents errors when ignored columns are present in the database.
|
||||
issuable_columns = with_cte ? issue_grouping_columns(use_cte: with_cte) : "#{table_name}.*"
|
||||
|
||||
extra_select_columns = extra_select_columns.unshift("(#{highest_priority}) AS highest_priority")
|
||||
extra_select_columns.unshift("(#{highest_priority}) AS highest_priority")
|
||||
|
||||
select(issuable_columns)
|
||||
.select(extra_select_columns)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ class GroupMember < Member
|
|||
SOURCE_TYPE = 'Namespace'
|
||||
|
||||
belongs_to :group, foreign_key: 'source_id'
|
||||
|
||||
alias_attribute :namespace_id, :source_id
|
||||
delegate :update_two_factor_requirement, to: :user
|
||||
|
||||
# Make sure group member points only to group as it source
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ class ProjectMember < Member
|
|||
|
||||
belongs_to :project, foreign_key: 'source_id'
|
||||
|
||||
delegate :namespace_id, to: :project
|
||||
|
||||
# Make sure project member points only to project as it source
|
||||
default_value_for :source_type, SOURCE_TYPE
|
||||
validates :source_type, format: { with: /\AProject\z/ }
|
||||
|
|
|
|||
|
|
@ -1350,8 +1350,8 @@ class MergeRequest < ApplicationRecord
|
|||
has_no_commits? || branch_missing? || cannot_be_merged?
|
||||
end
|
||||
|
||||
def can_be_merged_by?(user)
|
||||
access = ::Gitlab::UserAccess.new(user, container: project)
|
||||
def can_be_merged_by?(user, skip_collaboration_check: false)
|
||||
access = ::Gitlab::UserAccess.new(user, container: project, skip_collaboration_check: skip_collaboration_check)
|
||||
access.can_update_branch?(target_branch)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2711,7 +2711,7 @@ class Project < ApplicationRecord
|
|||
# Issue for N+1: https://gitlab.com/gitlab-org/gitlab-foss/issues/49322
|
||||
Gitlab::GitalyClient.allow_n_plus_1_calls do
|
||||
merge_requests_allowing_collaboration(branch_name).any? do |merge_request|
|
||||
merge_request.can_be_merged_by?(user)
|
||||
merge_request.can_be_merged_by?(user, skip_collaboration_check: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -93,10 +93,6 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
|
|||
filename_path(repository.license_blob&.name)
|
||||
end
|
||||
|
||||
def ci_configuration_path
|
||||
filename_path(repository.gitlab_ci_yml&.name)
|
||||
end
|
||||
|
||||
def contribution_guide_path
|
||||
if project && contribution_guide = repository.contribution_guide
|
||||
project_blob_path(
|
||||
|
|
@ -131,10 +127,6 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
|
|||
ide_edit_path(project, default_branch_or_master, 'CONTRIBUTING.md')
|
||||
end
|
||||
|
||||
def add_ci_yml_path
|
||||
add_special_file_path(file_name: ci_config_path_or_default)
|
||||
end
|
||||
|
||||
def add_readme_path
|
||||
add_special_file_path(file_name: 'README.md')
|
||||
end
|
||||
|
|
@ -384,11 +376,11 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
|
|||
if cicd_missing?
|
||||
AnchorData.new(false,
|
||||
statistic_icon + _('Set up CI/CD'),
|
||||
add_ci_yml_path)
|
||||
project_ci_pipeline_editor_path(project))
|
||||
elsif repository.gitlab_ci_yml.present?
|
||||
AnchorData.new(false,
|
||||
statistic_icon('doc-text') + _('CI/CD configuration'),
|
||||
ci_configuration_path,
|
||||
project_ci_pipeline_editor_path(project),
|
||||
'btn-default')
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,13 +10,14 @@ module Members
|
|||
|
||||
@errors = {}
|
||||
@emails = params[:email]&.split(',')&.uniq&.flatten
|
||||
@source = params[:source]
|
||||
end
|
||||
|
||||
def execute(source)
|
||||
@source = source
|
||||
def execute
|
||||
validate_emails!
|
||||
|
||||
emails.each(&method(:process_email))
|
||||
enqueue_onboarding_progress_action
|
||||
result
|
||||
rescue BlankEmailsError, TooManyEmailsError => e
|
||||
error(e.message)
|
||||
|
|
@ -24,7 +25,7 @@ module Members
|
|||
|
||||
private
|
||||
|
||||
attr_reader :source, :errors, :emails
|
||||
attr_reader :source, :errors, :emails, :member_created_namespace_id
|
||||
|
||||
def validate_emails!
|
||||
raise BlankEmailsError, s_('AddMember|Email cannot be blank') if emails.blank?
|
||||
|
|
@ -88,6 +89,7 @@ module Members
|
|||
errors[email] = new_member.errors.full_messages.to_sentence
|
||||
else
|
||||
after_execute(member: new_member)
|
||||
@member_created_namespace_id ||= new_member.namespace_id
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -98,6 +100,12 @@ module Members
|
|||
success
|
||||
end
|
||||
end
|
||||
|
||||
def enqueue_onboarding_progress_action
|
||||
return unless member_created_namespace_id
|
||||
|
||||
Namespaces::OnboardingUserAddedWorker.perform_async(member_created_namespace_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ module Projects
|
|||
if fork_network = @project.root_of_fork_network
|
||||
fork_network.update(root_project: nil, deleted_root_project_name: @project.full_name)
|
||||
end
|
||||
|
||||
@project.leave_pool_repository
|
||||
end
|
||||
|
||||
# rubocop: disable Cop/InBatches
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
= s_('Jobs|Use jobs to automate your tasks')
|
||||
%p
|
||||
= s_('Jobs|Jobs are the building blocks of a GitLab CI/CD pipeline. Each job has a specific task, like testing code. To set up jobs in a CI/CD pipeline, add a CI/CD configuration file to your project.')
|
||||
= link_to s_('Jobs|Create CI/CD configuration file'), @project.present(current_user: current_user).add_ci_yml_path, class: 'btn gl-button btn-info js-empty-state-button'
|
||||
= link_to s_('Jobs|Create CI/CD configuration file'), project_ci_pipeline_editor_path(project), class: 'btn gl-button btn-info js-empty-state-button'
|
||||
- else
|
||||
.nothing-here-block= s_('Jobs|No jobs to show')
|
||||
- else
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
- project = local_assigns.fetch(:project)
|
||||
- model = local_assigns.fetch(:model)
|
||||
- form = local_assigns.fetch(:form)
|
||||
- placeholder = model.is_a?(MergeRequest) ? _('Describe the goal of the changes and what reviewers should be aware of.') : _('Write a comment or drag your files here…')
|
||||
- placeholder = model.is_a?(MergeRequest) ? _('Describe the goal of the changes and what reviewers should be aware of.') : _('Write a description or drag your files here…')
|
||||
|
||||
- supports_quick_actions = true
|
||||
- preview_url = preview_markdown_path(project, target_type: model.class.name)
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@
|
|||
= clipboard_button(text: source_branch, title: _('Copy branch name'), placement: "left", boundary: 'viewport')
|
||||
.gl-display-flex.gl-align-items-center.gl-justify-content-space-between.gl-mb-2.hide-collapsed
|
||||
%span.gl-overflow-hidden.gl-text-overflow-ellipsis.gl-white-space-nowrap
|
||||
= _('Source branch: %{source_branch_open}%{source_branch}%{source_branch_close}').html_safe % { source_branch_open: "<span class='gl-font-monospace' title='#{source_branch}'>".html_safe, source_branch_close: "</span>".html_safe, source_branch: source_branch }
|
||||
= _('Source branch: %{source_branch_open}%{source_branch}%{source_branch_close}').html_safe % { source_branch_open: "<span class='gl-font-monospace' data-testid='ref-name' title='#{html_escape(source_branch)}'>".html_safe, source_branch_close: "</span>".html_safe, source_branch: html_escape(source_branch) }
|
||||
= clipboard_button(text: source_branch, title: _('Copy branch name'), placement: "left", boundary: 'viewport')
|
||||
|
||||
- if show_forwarding_email
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ class ExpireJobCacheWorker
|
|||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def perform(job_id)
|
||||
job = CommitStatus.joins(:pipeline, :project).find_by(id: job_id)
|
||||
job = CommitStatus.eager_load_pipeline.find_by(id: job_id)
|
||||
return unless job
|
||||
|
||||
pipeline = job.pipeline
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update New Issue form description copy from 'wite a comment' to 'wite a description'
|
||||
merge_request: 58068
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add enqueueing of Onboarding Progress to the Invite Service
|
||||
merge_request: 57372
|
||||
author:
|
||||
type: other
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Deprecate btn-warning on admin area delete user modal
|
||||
merge_request: 57761
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: 'Migration: Add cloud column to licenses'
|
||||
merge_request: 57781
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Redirect deprecated pipeline routes
|
||||
merge_request: 53990
|
||||
author:
|
||||
type: removed
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Switch to using a fake mimemagic gem
|
||||
merge_request: 57443
|
||||
author:
|
||||
type: other
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fixes rubocop offenses Style/RedundantSelfAssignment
|
||||
merge_request: 57920
|
||||
author: Shubham Kumar (@imskr)
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Reduce query count for popular worker ExpireJobCacheWorker
|
||||
merge_request: 57773
|
||||
author:
|
||||
type: performance
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Redirect to the pipeline editor when clicking on CI/CD quick links
|
||||
merge_request: 57085
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Refactor MimeMagic calls to new MimeType class
|
||||
merge_request: 57421
|
||||
author:
|
||||
type: other
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Remove direct mimemagic dependency
|
||||
merge_request: 57387
|
||||
author:
|
||||
type: other
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Make HipChat project service do nothing
|
||||
merge_request: 57434
|
||||
author:
|
||||
type: removed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update ruby-magic-static to v0.3.5
|
||||
merge_request: 57984
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Ensure that locked attributes can not be changed using a counter.
|
||||
# TODO: this can be removed once `asciidoctor` gem is > 2.0.12
|
||||
# and https://github.com/asciidoctor/asciidoctor/issues/3939 is merged
|
||||
module Asciidoctor
|
||||
module DocumentPatch
|
||||
def counter(name, seed = nil)
|
||||
return @parent_document.counter(name, seed) if @parent_document # rubocop: disable Gitlab/ModuleWithInstanceVariables
|
||||
|
||||
unless attribute_locked? name
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Asciidoctor::Document
|
||||
prepend Asciidoctor::DocumentPatch
|
||||
end
|
||||
|
|
@ -553,7 +553,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
|
|||
# Deprecated unscoped routing.
|
||||
scope as: 'deprecated' do
|
||||
# Issue https://gitlab.com/gitlab-org/gitlab/issues/118849
|
||||
draw :pipelines
|
||||
draw :repository
|
||||
|
||||
# Issue https://gitlab.com/gitlab-org/gitlab/-/issues/29572
|
||||
|
|
@ -576,7 +575,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
|
|||
:environments, :protected_environments, :error_tracking, :alert_management,
|
||||
:tracing,
|
||||
:serverless, :clusters, :audit_events, :wikis, :merge_requests,
|
||||
:vulnerability_feedback, :security, :dependencies, :issues)
|
||||
:vulnerability_feedback, :security, :dependencies, :issues,
|
||||
:pipelines, :pipeline_schedules)
|
||||
end
|
||||
|
||||
# rubocop: disable Cop/PutProjectRoutesUnderScope
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddCloudToLicenses < ActiveRecord::Migration[6.0]
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
add_column :licenses, :cloud, :boolean, default: false
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
a435a211d7e8b9a972323769299fc6e537fdeaa127f8db6ab53031901a51ec36
|
||||
|
|
@ -14182,7 +14182,8 @@ CREATE TABLE licenses (
|
|||
id integer NOT NULL,
|
||||
data text NOT NULL,
|
||||
created_at timestamp without time zone,
|
||||
updated_at timestamp without time zone
|
||||
updated_at timestamp without time zone,
|
||||
cloud boolean DEFAULT false
|
||||
);
|
||||
|
||||
CREATE SEQUENCE licenses_id_seq
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ This section is for links to information elsewhere in the GitLab documentation.
|
|||
- Storing data in another location.
|
||||
- Destructively reseeding the GitLab database.
|
||||
- Guidance around updating packaged PostgreSQL, including how to stop it
|
||||
happening automatically.
|
||||
from happening automatically.
|
||||
|
||||
- [Information about external PostgreSQL](../postgresql/external.md).
|
||||
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ Example response:
|
|||
## Test system hook
|
||||
|
||||
```plaintext
|
||||
GET /hooks/:id
|
||||
POST /hooks/:id
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|
|
@ -98,7 +98,7 @@ GET /hooks/:id
|
|||
Example request:
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/hooks/2"
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/hooks/1"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
|
|
|||
|
|
@ -150,16 +150,13 @@ at GitLab so far:
|
|||
|
||||
## Limitations
|
||||
|
||||
- Danger output is not added to a merge request comment if working on
|
||||
a fork. This happens because the secret variable from the canonical
|
||||
project is not shared to forks.
|
||||
To work around this, you can add an [environment
|
||||
variable](../ci/variables/README.md) called
|
||||
`DANGER_GITLAB_API_TOKEN` with a personal API token to your
|
||||
fork. That way the danger comments are made from CI using that
|
||||
API token instead.
|
||||
Making the variable
|
||||
[masked](../ci/variables/README.md#mask-a-custom-variable) makes sure
|
||||
it doesn't show up in the job logs. The variable cannot be
|
||||
[protected](../ci/variables/README.md#protect-a-custom-variable),
|
||||
as it needs to be present for all feature branches.
|
||||
Danger is run but its output is not added to a merge request comment if working
|
||||
on a fork. This happens because the secret variable from the canonical project
|
||||
is not shared to forks. To work around this, you can add an [environment
|
||||
variable](../ci/variables/README.md) called `DANGER_GITLAB_API_TOKEN` with a
|
||||
personal API token to your fork. That way the danger comments are made from CI
|
||||
using that API token instead. Making the variable
|
||||
[masked](../ci/variables/README.md#mask-a-custom-variable) makes sure it
|
||||
doesn't show up in the job logs. The variable cannot be
|
||||
[protected](../ci/variables/README.md#protect-a-custom-variable), as it needs
|
||||
to be present for all feature branches.
|
||||
|
|
|
|||
|
|
@ -896,18 +896,56 @@ On GitLab.com, we have DangerBot setup to monitor Product Intelligence related f
|
|||
|
||||
On GitLab.com, the Product Intelligence team regularly monitors Usage Ping. They may alert you that your metrics need further optimization to run quicker and with greater success. You may also use the [Usage Ping QA dashboard](https://app.periscopedata.com/app/gitlab/632033/Usage-Ping-QA) to check how well your metric performs. The dashboard allows filtering by GitLab version, by "Self-managed" & "SaaS" and shows you how many failures have occurred for each metric. Whenever you notice a high failure rate, you may re-optimize your metric.
|
||||
|
||||
### Optional: Test Prometheus based Usage Ping
|
||||
### Usage Ping local setup
|
||||
|
||||
If the data submitted includes metrics [queried from Prometheus](#prometheus-queries) that you would like to inspect and verify,
|
||||
then you need to ensure that a Prometheus server is running locally, and that furthermore the respective GitLab components
|
||||
are exporting metrics to it. If you do not need to test data coming from Prometheus, no further action
|
||||
To set up Usage Ping locally, you must:
|
||||
|
||||
1. [Set up local repositories]#(set-up-local-repositories)
|
||||
1. [Test local setup](#test-local-setup)
|
||||
1. (Optional) [Test Prometheus-based usage ping](#test-prometheus-based-usage-ping)
|
||||
|
||||
#### Set up local repositories
|
||||
|
||||
1. Clone and start [GitLab](https://gitlab.com/gitlab-org/gitlab-development-kit).
|
||||
1. Clone and start [Versions Application](https://gitlab.com/gitlab-services/version-gitlab-com).
|
||||
Make sure to run `docker-compose up` to start a PostgreSQL and Redis instance.
|
||||
1. Point GitLab to the Versions Application endpoint instead of the default endpoint:
|
||||
1. Open [submit_usage_ping_service.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/services/submit_usage_ping_service.rb#L4) in your local and modified `PRODUCTION_URL`.
|
||||
1. Set it to the local Versions Application URL `http://localhost:3000/usage_data`.
|
||||
|
||||
#### Test local setup
|
||||
|
||||
1. Using the `gitlab` Rails console, manually trigger a usage ping:
|
||||
|
||||
```ruby
|
||||
SubmitUsagePingService.new.execute
|
||||
```
|
||||
|
||||
1. Use the `versions` Rails console to check the usage ping was successfully received,
|
||||
parsed, and stored in the Versions database:
|
||||
|
||||
```ruby
|
||||
UsageData.last
|
||||
```
|
||||
|
||||
### Test Prometheus-based usage ping
|
||||
|
||||
If the data submitted includes metrics [queried from Prometheus](#prometheus-queries)
|
||||
you want to inspect and verify, you must:
|
||||
|
||||
- Ensure that a Prometheus server is running locally.
|
||||
- Ensure the respective GitLab components are exporting metrics to the Prometheus server.
|
||||
|
||||
If you do not need to test data coming from Prometheus, no further action
|
||||
is necessary. Usage Ping should degrade gracefully in the absence of a running Prometheus server.
|
||||
|
||||
There are three kinds of components that may export data to Prometheus, and which are included in Usage Ping:
|
||||
Three kinds of components may export data to Prometheus, and are included in Usage Ping:
|
||||
|
||||
- [`node_exporter`](https://github.com/prometheus/node_exporter) - Exports node metrics from the host machine
|
||||
- [`gitlab-exporter`](https://gitlab.com/gitlab-org/gitlab-exporter) - Exports process metrics from various GitLab components
|
||||
- various GitLab services such as Sidekiq and the Rails server that export their own metrics
|
||||
- [`node_exporter`](https://github.com/prometheus/node_exporter): Exports node metrics
|
||||
from the host machine.
|
||||
- [`gitlab-exporter`](https://gitlab.com/gitlab-org/gitlab-exporter): Exports process metrics
|
||||
from various GitLab components.
|
||||
- Other various GitLab services, such as Sidekiq and the Rails server, which export their own metrics.
|
||||
|
||||
#### Test with an Omnibus container
|
||||
|
||||
|
|
|
|||
|
|
@ -6,30 +6,18 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Jira integrations **(FREE)**
|
||||
|
||||
GitLab can be integrated with [Jira](https://www.atlassian.com/software/jira).
|
||||
If your organization uses [Jira](https://www.atlassian.com/software/jira) issues,
|
||||
you can [migrate](../../../user/project/import/jira.md) your issues from Jira and work
|
||||
exclusively in GitLab.
|
||||
|
||||
[Issues](../issues/index.md) are a tool for discussing ideas, and planning and tracking work.
|
||||
However, your organization may already use Jira for these purposes, with extensive, established data
|
||||
and business processes they rely on.
|
||||
However, if you'd like to continue to use Jira, you can integrate it with GitLab.
|
||||
|
||||
Although you can [migrate](../../../user/project/import/jira.md) your Jira issues and work
|
||||
exclusively in GitLab, you can also continue to use Jira by using the GitLab Jira integrations.
|
||||
There are two ways to use GitLab with Jira:
|
||||
|
||||
## Integration types
|
||||
|
||||
There are two different Jira integrations that allow different types of cross-referencing between
|
||||
GitLab activity and Jira issues, with additional features:
|
||||
|
||||
- [Jira integration](jira.md), built in to GitLab. In a given GitLab project, it can be configured
|
||||
to connect to any Jira instance, either hosted by you or hosted in
|
||||
[Atlassian cloud](https://www.atlassian.com/cloud).
|
||||
- [Jira development panel integration](../../../integration/jira/index.md). Connects all
|
||||
GitLab projects under a specified group or personal namespace.
|
||||
|
||||
Jira development panel integration configuration depends on whether:
|
||||
|
||||
- You're using GitLab.com or a self-managed GitLab instance.
|
||||
- You're using Jira on [Atlassian cloud](https://www.atlassian.com/cloud) or on your own server.
|
||||
- [Jira integration](jira.md). Connect a GitLab project
|
||||
to a Jira instance. The Jira instance can be hosted by you or in [Atlassian cloud](https://www.atlassian.com/cloud).
|
||||
- [Jira Development panel integration](../../../integration/jira_development_panel.md).
|
||||
Connect all GitLab projects under a group or personal namespace.
|
||||
|
||||
The integration you choose depends on the capabilities you require.
|
||||
You can also install both at the same time.
|
||||
|
|
|
|||
|
|
@ -25,11 +25,11 @@ module API
|
|||
optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
|
||||
end
|
||||
post ":id/invitations" do
|
||||
source = find_source(source_type, params[:id])
|
||||
params[:source] = find_source(source_type, params[:id])
|
||||
|
||||
authorize_admin_source!(source_type, source)
|
||||
authorize_admin_source!(source_type, params[:source])
|
||||
|
||||
::Members::InviteService.new(current_user, params).execute(source)
|
||||
::Members::InviteService.new(current_user, params).execute
|
||||
end
|
||||
|
||||
desc 'Get a list of group or project invitations viewable by the authenticated user' do
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ module API
|
|||
params do
|
||||
requires :id, type: Integer, desc: 'The ID of the system hook'
|
||||
end
|
||||
get ":id" do
|
||||
post ":id" do
|
||||
hook = SystemHook.find(params[:id])
|
||||
data = {
|
||||
event_name: "project_create",
|
||||
|
|
|
|||
|
|
@ -69,8 +69,12 @@ module Gitlab
|
|||
end
|
||||
|
||||
def validate_service_request
|
||||
headers = {}
|
||||
headers['X-Gitlab-Token'] = validation_service_token if validation_service_token
|
||||
|
||||
Gitlab::HTTP.post(
|
||||
validation_service_url, timeout: validation_service_timeout,
|
||||
headers: headers,
|
||||
body: validation_service_payload.to_json
|
||||
)
|
||||
end
|
||||
|
|
@ -86,6 +90,10 @@ module Gitlab
|
|||
ENV['EXTERNAL_VALIDATION_SERVICE_URL']
|
||||
end
|
||||
|
||||
def validation_service_token
|
||||
ENV['EXTERNAL_VALIDATION_SERVICE_TOKEN']
|
||||
end
|
||||
|
||||
def validation_service_payload
|
||||
{
|
||||
project: {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
module Gitlab
|
||||
module MarkdownCache
|
||||
# Increment this number every time the renderer changes its output
|
||||
CACHE_COMMONMARK_VERSION = 26
|
||||
CACHE_COMMONMARK_VERSION = 27
|
||||
CACHE_COMMONMARK_VERSION_START = 10
|
||||
|
||||
BaseError = Class.new(StandardError)
|
||||
|
|
|
|||
|
|
@ -11,10 +11,11 @@ module Gitlab
|
|||
attr_reader :user, :push_ability
|
||||
attr_accessor :container
|
||||
|
||||
def initialize(user, container: nil, push_ability: :push_code)
|
||||
def initialize(user, container: nil, push_ability: :push_code, skip_collaboration_check: false)
|
||||
@user = user
|
||||
@container = container
|
||||
@push_ability = push_ability
|
||||
@skip_collaboration_check = skip_collaboration_check
|
||||
end
|
||||
|
||||
def can_do_action?(action)
|
||||
|
|
@ -87,6 +88,8 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
attr_reader :skip_collaboration_check
|
||||
|
||||
def can_push?
|
||||
user.can?(push_ability, container)
|
||||
end
|
||||
|
|
@ -98,6 +101,8 @@ module Gitlab
|
|||
end
|
||||
|
||||
def branch_allows_collaboration_for?(ref)
|
||||
return false if skip_collaboration_check
|
||||
|
||||
# Checking for an internal project or group to prevent an infinite loop:
|
||||
# https://gitlab.com/gitlab-org/gitlab/issues/36805
|
||||
(!project.internal? && project.branch_allows_collaboration?(user, ref))
|
||||
|
|
|
|||
|
|
@ -34656,6 +34656,9 @@ msgstr ""
|
|||
msgid "Write a comment…"
|
||||
msgstr ""
|
||||
|
||||
msgid "Write a description or drag your files here…"
|
||||
msgstr ""
|
||||
|
||||
msgid "Write milestone description..."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -166,6 +166,26 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
# Method for selecting radios
|
||||
def choose_element(name, click_by_js = false)
|
||||
if find_element(name, visible: false).checked?
|
||||
QA::Runtime::Logger.debug("#{name} is already selected")
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
retry_until(sleep_interval: 1) do
|
||||
radio = find_element(name, visible: false)
|
||||
# Some radio buttons are hidden by their labels and cannot be clicked directly
|
||||
click_by_js ? page.execute_script("arguments[0].click();", radio) : radio.click
|
||||
selected = find_element(name, visible: false).checked?
|
||||
|
||||
QA::Runtime::Logger.debug(selected ? "#{name} was selected" : "#{name} was not selected")
|
||||
|
||||
selected
|
||||
end
|
||||
end
|
||||
|
||||
# Use this to simulate moving the pointer to an element's coordinate
|
||||
# and sending a click event.
|
||||
# This is a helpful workaround when there is a transparent element overlapping
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@ module QA
|
|||
end
|
||||
|
||||
def click_save_changes
|
||||
click_element :save_merge_request_changes_button
|
||||
click_element(:save_merge_request_changes_button)
|
||||
end
|
||||
|
||||
def enable_ff_only
|
||||
click_element :merge_ff_radio_button
|
||||
click_element(:merge_ff_radio_button)
|
||||
click_save_changes
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ module QA
|
|||
end
|
||||
|
||||
def fill_element(name, content)
|
||||
masked_content = name.to_s.include?('password') ? '*****' : content
|
||||
masked_content = name.to_s.match?(/token|key|password/) ? '*****' : content
|
||||
|
||||
log(%Q(filling :#{name} with "#{masked_content}"))
|
||||
|
||||
|
|
|
|||
|
|
@ -136,8 +136,10 @@ postgresql:
|
|||
metrics:
|
||||
enabled: false
|
||||
resources:
|
||||
requests:
|
||||
cpu: 600m
|
||||
memory: 1000M
|
||||
limits:
|
||||
cpu: 1300m
|
||||
memory: 1500M
|
||||
prometheus:
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ RSpec.describe 'Database schema' do
|
|||
# postgres and mysql both automatically create an index on the primary
|
||||
# key. Also, the rails connection.indexes() method does not return
|
||||
# automatically generated indexes (like the primary key index).
|
||||
first_indexed_column = first_indexed_column.push(primary_key_column)
|
||||
first_indexed_column.push(primary_key_column)
|
||||
|
||||
expect(first_indexed_column.uniq).to include(*foreign_keys_columns)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ FactoryBot.define do
|
|||
state { :none }
|
||||
|
||||
before(:create) do |pool|
|
||||
pool.source_project = create(:project, :repository)
|
||||
pool.source_project ||= create(:project, :repository)
|
||||
pool.source_project.update!(pool_repository: pool)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ RSpec.describe "User creates issue" do
|
|||
.and have_no_content("Milestone")
|
||||
|
||||
expect(page.find('#issue_title')['placeholder']).to eq 'Title'
|
||||
expect(page.find('#issue_description')['placeholder']).to eq 'Write a comment or drag your files here…'
|
||||
expect(page.find('#issue_description')['placeholder']).to eq 'Write a description or drag your files here…'
|
||||
end
|
||||
|
||||
issue_title = "500 error on profile"
|
||||
|
|
|
|||
|
|
@ -111,4 +111,21 @@ RSpec.describe 'User views an open merge request' do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'XSS source branch' do
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
let(:source_branch) { "'><iframe/srcdoc=''></iframe>" }
|
||||
|
||||
before do
|
||||
project.repository.create_branch(source_branch, "master")
|
||||
|
||||
mr = create(:merge_request, source_project: project, target_project: project, source_branch: source_branch)
|
||||
|
||||
visit(merge_request_path(mr))
|
||||
end
|
||||
|
||||
it 'encodes branch name' do
|
||||
expect(find("[data-testid='ref-name']")[:title]).to eq(source_branch)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do
|
|||
|
||||
it 'shows the empty state page' do
|
||||
expect(page).to have_content('Use jobs to automate your tasks')
|
||||
expect(page).to have_link('Create CI/CD configuration file', href: project.present(current_user: user).add_ci_yml_path)
|
||||
expect(page).to have_link('Create CI/CD configuration file', href: project_ci_pipeline_editor_path(project))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -226,11 +226,11 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do
|
|||
expect(project.repository.gitlab_ci_yml).to be_nil
|
||||
|
||||
page.within('.project-buttons') do
|
||||
expect(page).to have_link('Set up CI/CD', href: presenter.add_ci_yml_path)
|
||||
expect(page).to have_link('Set up CI/CD', href: project_ci_pipeline_editor_path(project))
|
||||
end
|
||||
end
|
||||
|
||||
it 'no "Set up CI/CD" button if the project already has a .gitlab-ci.yml' do
|
||||
it '"Set up CI/CD" button is renamed if the project already has a .gitlab-ci.yml' do
|
||||
Files::CreateService.new(
|
||||
project,
|
||||
project.creator,
|
||||
|
|
@ -247,6 +247,7 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do
|
|||
|
||||
page.within('.project-buttons') do
|
||||
expect(page).not_to have_link('Set up CI/CD')
|
||||
expect(page).to have_link('CI/CD configuration')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -50,11 +50,11 @@ exports[`User Operation confirmation modal renders modal with form included 1`]
|
|||
|
||||
<gl-button-stub
|
||||
buttontextclasses=""
|
||||
category="primary"
|
||||
category="secondary"
|
||||
disabled="true"
|
||||
icon=""
|
||||
size="medium"
|
||||
variant="warning"
|
||||
variant="danger"
|
||||
>
|
||||
|
||||
secondaryAction
|
||||
|
|
|
|||
|
|
@ -11,15 +11,15 @@ describe('User Operation confirmation modal', () => {
|
|||
let wrapper;
|
||||
let formSubmitSpy;
|
||||
|
||||
const findButton = (variant) =>
|
||||
const findButton = (variant, category) =>
|
||||
wrapper
|
||||
.findAll(GlButton)
|
||||
.filter((w) => w.attributes('variant') === variant)
|
||||
.filter((w) => w.attributes('variant') === variant && w.attributes('category') === category)
|
||||
.at(0);
|
||||
const findForm = () => wrapper.find('form');
|
||||
const findUsernameInput = () => wrapper.find(GlFormInput);
|
||||
const findPrimaryButton = () => findButton('danger');
|
||||
const findSecondaryButton = () => findButton('warning');
|
||||
const findPrimaryButton = () => findButton('danger', 'primary');
|
||||
const findSecondaryButton = () => findButton('danger', 'secondary');
|
||||
const findAuthenticityToken = () => new FormData(findForm().element).get('authenticity_token');
|
||||
const getUsername = () => findUsernameInput().attributes('value');
|
||||
const getMethodParam = () => new FormData(findForm().element).get('_method');
|
||||
|
|
|
|||
|
|
@ -92,6 +92,15 @@ module Gitlab
|
|||
expect(render(data[:input], context)).to include(data[:output])
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not allow locked attributes to be overridden' do
|
||||
input = <<~ADOC
|
||||
{counter:max-include-depth:1234}
|
||||
<|-- {max-include-depth}
|
||||
ADOC
|
||||
|
||||
expect(render(input, {})).not_to include('1234')
|
||||
end
|
||||
end
|
||||
|
||||
context "images" do
|
||||
|
|
@ -543,6 +552,40 @@ module Gitlab
|
|||
|
||||
expect(render(input, context)).to include(output.strip)
|
||||
end
|
||||
|
||||
it 'does not allow kroki-plantuml-include to be overridden' do
|
||||
input = <<~ADOC
|
||||
[plantuml, test="{counter:kroki-plantuml-include:/etc/passwd}", format="png"]
|
||||
....
|
||||
class BlockProcessor
|
||||
|
||||
BlockProcessor <|-- {counter:kroki-plantuml-include}
|
||||
....
|
||||
ADOC
|
||||
|
||||
output = <<~HTML
|
||||
<div>
|
||||
<div>
|
||||
<a class=\"no-attachment-icon\" href=\"https://kroki.io/plantuml/png/eNpLzkksLlZwyslPzg4oyk9OLS7OL-LiQuUr2NTo6ipUJ-eX5pWkFlllF-VnZ-oW5CTmlZTm5uhm5iXnlKak1gIABQEb8A==\" target=\"_blank\" rel=\"noopener noreferrer\"><img src=\"data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"Diagram\" class=\"lazy\" data-src=\"https://kroki.io/plantuml/png/eNpLzkksLlZwyslPzg4oyk9OLS7OL-LiQuUr2NTo6ipUJ-eX5pWkFlllF-VnZ-oW5CTmlZTm5uhm5iXnlKak1gIABQEb8A==\"></a>
|
||||
</div>
|
||||
</div>
|
||||
HTML
|
||||
|
||||
expect(render(input, {})).to include(output.strip)
|
||||
end
|
||||
|
||||
it 'does not allow kroki-server-url to be overridden' do
|
||||
input = <<~ADOC
|
||||
[plantuml, test="{counter:kroki-server-url:evilsite}", format="png"]
|
||||
....
|
||||
class BlockProcessor
|
||||
|
||||
BlockProcessor
|
||||
....
|
||||
ADOC
|
||||
|
||||
expect(render(input, {})).not_to include('evilsite')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with Kroki and BlockDiag (additional format) enabled' do
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
|
|||
expect(::Gitlab::HTTP).to receive(:post) do |_url, params|
|
||||
expect(params[:body]).to match_schema('/external_validation')
|
||||
expect(params[:timeout]).to eq(described_class::DEFAULT_VALIDATION_REQUEST_TIMEOUT)
|
||||
expect(params[:headers]).to eq({})
|
||||
end
|
||||
|
||||
perform!
|
||||
|
|
@ -119,6 +120,20 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when EXTERNAL_VALIDATION_SERVICE_TOKEN is set' do
|
||||
before do
|
||||
stub_env('EXTERNAL_VALIDATION_SERVICE_TOKEN', '123')
|
||||
end
|
||||
|
||||
it 'passes token in X-Gitlab-Token header' do
|
||||
expect(::Gitlab::HTTP).to receive(:post) do |_url, params|
|
||||
expect(params[:headers]).to eq({ 'X-Gitlab-Token' => '123' })
|
||||
end
|
||||
|
||||
perform!
|
||||
end
|
||||
end
|
||||
|
||||
context 'when validation returns 200 OK' do
|
||||
before do
|
||||
stub_request(:post, validation_service_url).to_return(status: 200, body: "{}")
|
||||
|
|
|
|||
|
|
@ -216,6 +216,15 @@ RSpec.describe Gitlab::UserAccess do
|
|||
expect(access.can_merge_to_branch?(@branch.name)).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'when skip_collaboration_check is true' do
|
||||
let(:access) { described_class.new(user, container: project, skip_collaboration_check: true) }
|
||||
|
||||
it 'does not call Project#branch_allows_collaboration?' do
|
||||
expect(project).not_to receive(:branch_allows_collaboration?)
|
||||
expect(access.can_push_to_branch?('master')).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#can_create_tag?' do
|
||||
|
|
|
|||
|
|
@ -66,6 +66,12 @@ RSpec.describe GroupMember do
|
|||
|
||||
it_behaves_like 'members notifications', :group
|
||||
|
||||
describe '#namespace_id' do
|
||||
subject { build(:group_member, source_id: 1).namespace_id }
|
||||
|
||||
it { is_expected.to eq 1 }
|
||||
end
|
||||
|
||||
describe '#real_source_type' do
|
||||
subject { create(:group_member).real_source_type }
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ RSpec.describe ProjectMember do
|
|||
it { is_expected.to validate_inclusion_of(:access_level).in_array(Gitlab::Access.values) }
|
||||
end
|
||||
|
||||
describe 'delegations' do
|
||||
it { is_expected.to delegate_method(:namespace_id).to(:project) }
|
||||
end
|
||||
|
||||
describe '.access_level_roles' do
|
||||
it 'returns Gitlab::Access.options' do
|
||||
expect(described_class.access_level_roles).to eq(Gitlab::Access.options)
|
||||
|
|
|
|||
|
|
@ -5319,6 +5319,64 @@ RSpec.describe Project, factory_default: :keep do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#branch_allows_collaboration?' do
|
||||
context 'when there are open merge requests that have their source/target branches point to each other' do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
let_it_be(:developer) { create(:user) }
|
||||
let_it_be(:reporter) { create(:user) }
|
||||
let_it_be(:guest) { create(:user) }
|
||||
|
||||
before_all do
|
||||
create(
|
||||
:merge_request,
|
||||
target_project: project,
|
||||
target_branch: 'master',
|
||||
source_project: project,
|
||||
source_branch: 'merge-test',
|
||||
allow_collaboration: true
|
||||
)
|
||||
|
||||
create(
|
||||
:merge_request,
|
||||
target_project: project,
|
||||
target_branch: 'merge-test',
|
||||
source_project: project,
|
||||
source_branch: 'master',
|
||||
allow_collaboration: true
|
||||
)
|
||||
|
||||
project.add_developer(developer)
|
||||
project.add_reporter(reporter)
|
||||
project.add_guest(guest)
|
||||
end
|
||||
|
||||
shared_examples_for 'successful check' do
|
||||
it 'does not go into an infinite loop' do
|
||||
expect { project.branch_allows_collaboration?(user, 'master') }
|
||||
.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is a developer' do
|
||||
let(:user) { developer }
|
||||
|
||||
it_behaves_like 'successful check'
|
||||
end
|
||||
|
||||
context 'when user is a reporter' do
|
||||
let(:user) { reporter }
|
||||
|
||||
it_behaves_like 'successful check'
|
||||
end
|
||||
|
||||
context 'when user is a guest' do
|
||||
let(:user) { guest }
|
||||
|
||||
it_behaves_like 'successful check'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with cross project merge requests' do
|
||||
let(:user) { create(:user) }
|
||||
let(:target_project) { create(:project, :repository) }
|
||||
|
|
|
|||
|
|
@ -103,15 +103,15 @@ RSpec.describe API::SystemHooks do
|
|||
end
|
||||
end
|
||||
|
||||
describe "GET /hooks/:id" do
|
||||
it "returns hook by id" do
|
||||
get api("/hooks/#{hook.id}", admin)
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
describe 'POST /hooks/:id' do
|
||||
it "returns and trigger hook by id" do
|
||||
post api("/hooks/#{hook.id}", admin)
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(json_response['event_name']).to eq('project_create')
|
||||
end
|
||||
|
||||
it "returns 404 on failure" do
|
||||
get api("/hooks/404", admin)
|
||||
post api("/hooks/404", admin)
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -684,6 +684,26 @@ RSpec.describe 'project routing' do
|
|||
end
|
||||
end
|
||||
|
||||
describe Projects::PipelinesController, 'routing' do
|
||||
it 'to #index' do
|
||||
expect(get('/gitlab/gitlabhq/-/pipelines')).to route_to('projects/pipelines#index', namespace_id: 'gitlab', project_id: 'gitlabhq')
|
||||
end
|
||||
|
||||
it 'to #show' do
|
||||
expect(get('/gitlab/gitlabhq/-/pipelines/12')).to route_to('projects/pipelines#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '12')
|
||||
end
|
||||
|
||||
it_behaves_like 'redirecting a legacy path', '/gitlab/gitlabhq/pipelines', '/gitlab/gitlabhq/-/pipelines'
|
||||
end
|
||||
|
||||
describe Projects::PipelineSchedulesController, 'routing' do
|
||||
it 'to #index' do
|
||||
expect(get('/gitlab/gitlabhq/-/pipeline_schedules')).to route_to('projects/pipeline_schedules#index', namespace_id: 'gitlab', project_id: 'gitlabhq')
|
||||
end
|
||||
|
||||
it_behaves_like 'redirecting a legacy path', '/gitlab/gitlabhq/pipeline_schedules', '/gitlab/gitlabhq/-/pipeline_schedules'
|
||||
end
|
||||
|
||||
describe Projects::Settings::OperationsController, 'routing' do
|
||||
it 'to #reset_alerting_token' do
|
||||
expect(post('/gitlab/gitlabhq/-/settings/operations/reset_alerting_token')).to route_to('projects/settings/operations#reset_alerting_token', namespace_id: 'gitlab', project_id: 'gitlabhq')
|
||||
|
|
|
|||
|
|
@ -2,29 +2,43 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Members::InviteService, :aggregate_failures do
|
||||
RSpec.describe Members::InviteService, :aggregate_failures, :clean_gitlab_redis_shared_state, :sidekiq_inline do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:user) { project.owner }
|
||||
let_it_be(:project_user) { create(:user) }
|
||||
let_it_be(:namespace) { project.namespace }
|
||||
let(:params) { {} }
|
||||
let(:base_params) { { access_level: Gitlab::Access::GUEST } }
|
||||
let(:base_params) { { access_level: Gitlab::Access::GUEST, source: project } }
|
||||
|
||||
subject(:result) { described_class.new(user, base_params.merge(params)).execute(project) }
|
||||
subject(:result) { described_class.new(user, base_params.merge(params) ).execute }
|
||||
|
||||
context 'when email is previously unused by current members' do
|
||||
context 'when there is a valid member invited' do
|
||||
let(:params) { { email: 'email@example.org' } }
|
||||
|
||||
it 'successfully creates a member' do
|
||||
expect { result }.to change(ProjectMember, :count).by(1)
|
||||
expect_to_create_members(count: 1)
|
||||
expect(result[:status]).to eq(:success)
|
||||
end
|
||||
|
||||
it_behaves_like 'records an onboarding progress action', :user_added
|
||||
end
|
||||
|
||||
context 'when email is not a valid email' do
|
||||
let(:params) { { email: '_bogus_' } }
|
||||
|
||||
it 'returns an error' do
|
||||
expect_not_to_create_members
|
||||
expect(result[:message]['_bogus_']).to eq("Invite email is invalid")
|
||||
end
|
||||
|
||||
it_behaves_like 'does not record an onboarding progress action'
|
||||
end
|
||||
|
||||
context 'when emails are passed as an array' do
|
||||
let(:params) { { email: %w[email@example.org email2@example.org] } }
|
||||
|
||||
it 'successfully creates members' do
|
||||
expect { result }.to change(ProjectMember, :count).by(2)
|
||||
expect_to_create_members(count: 2)
|
||||
expect(result[:status]).to eq(:success)
|
||||
end
|
||||
end
|
||||
|
|
@ -33,33 +47,23 @@ RSpec.describe Members::InviteService, :aggregate_failures do
|
|||
let(:params) { { email: '' } }
|
||||
|
||||
it 'returns an error' do
|
||||
expect(result[:status]).to eq(:error)
|
||||
expect_not_to_create_members
|
||||
expect(result[:message]).to eq('Email cannot be blank')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when email param is not included' do
|
||||
it 'returns an error' do
|
||||
expect(result[:status]).to eq(:error)
|
||||
expect_not_to_create_members
|
||||
expect(result[:message]).to eq('Email cannot be blank')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when email is not a valid email' do
|
||||
let(:params) { { email: '_bogus_' } }
|
||||
|
||||
it 'returns an error' do
|
||||
expect { result }.not_to change(ProjectMember, :count)
|
||||
expect(result[:status]).to eq(:error)
|
||||
expect(result[:message]['_bogus_']).to eq("Invite email is invalid")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when duplicate email addresses are passed' do
|
||||
let(:params) { { email: 'email@example.org,email@example.org' } }
|
||||
|
||||
it 'only creates one member per unique address' do
|
||||
expect { result }.to change(ProjectMember, :count).by(1)
|
||||
expect_to_create_members(count: 1)
|
||||
expect(result[:status]).to eq(:success)
|
||||
end
|
||||
end
|
||||
|
|
@ -71,8 +75,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do
|
|||
let(:params) { { email: emails } }
|
||||
|
||||
it 'limits the number of emails to 100' do
|
||||
expect { result }.not_to change(ProjectMember, :count)
|
||||
expect(result[:status]).to eq(:error)
|
||||
expect_not_to_create_members
|
||||
expect(result[:message]).to eq('Too many users specified (limit is 100)')
|
||||
end
|
||||
end
|
||||
|
|
@ -81,8 +84,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do
|
|||
let(:params) { { email: 'email@example.org,email2@example.org', limit: 1 } }
|
||||
|
||||
it 'limits the number of emails to the limit supplied' do
|
||||
expect { result }.not_to change(ProjectMember, :count)
|
||||
expect(result[:status]).to eq(:error)
|
||||
expect_not_to_create_members
|
||||
expect(result[:message]).to eq('Too many users specified (limit is 1)')
|
||||
end
|
||||
end
|
||||
|
|
@ -91,7 +93,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do
|
|||
let(:params) { { email: emails, limit: -1 } }
|
||||
|
||||
it 'does not limit number of emails' do
|
||||
expect { result }.to change(ProjectMember, :count).by(101)
|
||||
expect_to_create_members(count: 101)
|
||||
expect(result[:status]).to eq(:success)
|
||||
end
|
||||
end
|
||||
|
|
@ -101,7 +103,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do
|
|||
let(:params) { { email: project_user.email } }
|
||||
|
||||
it 'adds an existing user to members' do
|
||||
expect { result }.to change(ProjectMember, :count).by(1)
|
||||
expect_to_create_members(count: 1)
|
||||
expect(result[:status]).to eq(:success)
|
||||
expect(project.users).to include project_user
|
||||
end
|
||||
|
|
@ -111,8 +113,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do
|
|||
let(:params) { { email: project_user.email, access_level: -1 } }
|
||||
|
||||
it 'returns an error' do
|
||||
expect { result }.not_to change(ProjectMember, :count)
|
||||
expect(result[:status]).to eq(:error)
|
||||
expect_not_to_create_members
|
||||
expect(result[:message][project_user.email]).to eq("Access level is not included in the list")
|
||||
end
|
||||
end
|
||||
|
|
@ -122,7 +123,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do
|
|||
let(:params) { { email: "#{invited_member.invite_email},#{project_user.email}" } }
|
||||
|
||||
it 'adds new email and returns an error for the already invited email' do
|
||||
expect { result }.to change(ProjectMember, :count).by(1)
|
||||
expect_to_create_members(count: 1)
|
||||
expect(result[:status]).to eq(:error)
|
||||
expect(result[:message][invited_member.invite_email]).to eq("Member already invited to #{project.name}")
|
||||
expect(project.users).to include project_user
|
||||
|
|
@ -134,7 +135,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do
|
|||
let(:params) { { email: "#{requested_member.user.email},#{project_user.email}" } }
|
||||
|
||||
it 'adds new email and returns an error for the already invited email' do
|
||||
expect { result }.to change(ProjectMember, :count).by(1)
|
||||
expect_to_create_members(count: 1)
|
||||
expect(result[:status]).to eq(:error)
|
||||
expect(result[:message][requested_member.user.email])
|
||||
.to eq("Member cannot be invited because they already requested to join #{project.name}")
|
||||
|
|
@ -147,10 +148,19 @@ RSpec.describe Members::InviteService, :aggregate_failures do
|
|||
let(:params) { { email: "#{existing_member.user.email},#{project_user.email}" } }
|
||||
|
||||
it 'adds new email and returns an error for the already invited email' do
|
||||
expect { result }.to change(ProjectMember, :count).by(1)
|
||||
expect_to_create_members(count: 1)
|
||||
expect(result[:status]).to eq(:error)
|
||||
expect(result[:message][existing_member.user.email]).to eq("Already a member of #{project.name}")
|
||||
expect(project.users).to include project_user
|
||||
end
|
||||
end
|
||||
|
||||
def expect_to_create_members(count:)
|
||||
expect { result }.to change(ProjectMember, :count).by(count)
|
||||
end
|
||||
|
||||
def expect_not_to_create_members
|
||||
expect { result }.not_to change(ProjectMember, :count)
|
||||
expect(result[:status]).to eq(:error)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -403,7 +403,7 @@ RSpec.describe Projects::ForkService do
|
|||
end
|
||||
|
||||
context 'when forking with object pools' do
|
||||
let(:fork_from_project) { create(:project, :public) }
|
||||
let(:fork_from_project) { create(:project, :repository, :public) }
|
||||
let(:forker) { create(:user) }
|
||||
|
||||
context 'when no pool exists' do
|
||||
|
|
|
|||
|
|
@ -207,6 +207,17 @@ RSpec.describe Projects::UnlinkForkService, :use_clean_rails_memory_store_cachin
|
|||
end
|
||||
end
|
||||
|
||||
context 'a project with pool repository' do
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
let!(:pool_repository) { create(:pool_repository, :ready, source_project: project) }
|
||||
|
||||
subject { described_class.new(project, user) }
|
||||
|
||||
it 'when unlinked leaves pool repository' do
|
||||
expect { subject.execute }.to change { project.reload.has_pool_repository? }.from(true).to(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when given project is not part of a fork network' do
|
||||
let!(:project_without_forks) { create(:project, :public) }
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ RSpec.describe ExpireJobCacheWorker do
|
|||
|
||||
describe '#perform' do
|
||||
context 'with a job in the pipeline' do
|
||||
let(:job) { create(:ci_build, pipeline: pipeline) }
|
||||
let_it_be(:job) { create(:ci_build, pipeline: pipeline) }
|
||||
|
||||
let(:job_args) { job.id }
|
||||
|
||||
include_examples 'an idempotent worker' do
|
||||
|
|
@ -31,6 +32,24 @@ RSpec.describe ExpireJobCacheWorker do
|
|||
subject
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not perform extra queries', :aggregate_failures do
|
||||
worker = described_class.new
|
||||
recorder = ActiveRecord::QueryRecorder.new { worker.perform(job.id) }
|
||||
|
||||
occurences = recorder.data.values.flat_map {|v| v[:occurrences]}
|
||||
project_queries = occurences.select {|s| s.include?('FROM "projects"')}
|
||||
namespace_queries = occurences.select {|s| s.include?('FROM "namespaces"')}
|
||||
route_queries = occurences.select {|s| s.include?('FROM "routes"')}
|
||||
|
||||
# This worker is run 1 million times an hour, so we need to save as much
|
||||
# queries as possible.
|
||||
expect(recorder.count).to be <= 1
|
||||
|
||||
expect(project_queries.size).to eq(0)
|
||||
expect(namespace_queries.size).to eq(0)
|
||||
expect(route_queries.size).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is no job in the pipeline' do
|
||||
|
|
|
|||
Loading…
Reference in New Issue