Commit e9c2bf26 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 946771d0
...@@ -26,6 +26,10 @@ export default (resolvers = {}, config = {}) => { ...@@ -26,6 +26,10 @@ export default (resolvers = {}, config = {}) => {
headers: { headers: {
[csrf.headerKey]: csrf.token, [csrf.headerKey]: csrf.token,
}, },
// fetch won’t send cookies in older browsers, unless you set the credentials init option.
// We set to `same-origin` which is default value in modern browsers.
// See https://github.com/whatwg/fetch/pull/585 for more information.
credentials: 'same-origin',
}; };
return new ApolloClient({ return new ApolloClient({
......
...@@ -39,7 +39,7 @@ class Projects::ForksController < Projects::ApplicationController ...@@ -39,7 +39,7 @@ class Projects::ForksController < Projects::ApplicationController
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def new def new
@namespaces = fork_service.valid_fork_targets @namespaces = fork_service.valid_fork_targets - [project.namespace]
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
......
...@@ -8,7 +8,7 @@ class ForkTargetsFinder ...@@ -8,7 +8,7 @@ class ForkTargetsFinder
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def execute def execute
::Namespace.where(id: user.manageable_namespaces).where.not(id: project.namespace).sort_by_type ::Namespace.where(id: user.manageable_namespaces).sort_by_type
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
...@@ -62,6 +62,10 @@ module Ci ...@@ -62,6 +62,10 @@ module Ci
end end
end end
def has_downstream_pipeline?
sourced_pipelines.exists?
end
def downstream_pipeline_params def downstream_pipeline_params
return child_params if triggers_child_pipeline? return child_params if triggers_child_pipeline?
return cross_project_params if downstream_project.present? return cross_project_params if downstream_project.present?
......
...@@ -227,6 +227,7 @@ module Ci ...@@ -227,6 +227,7 @@ module Ci
end end
after_transition created: :pending do |pipeline| after_transition created: :pending do |pipeline|
next if Feature.enabled?(:ci_drop_bridge_on_downstream_errors, pipeline.project, default_enabled: true)
next unless pipeline.bridge_triggered? next unless pipeline.bridge_triggered?
next if pipeline.bridge_waiting? next if pipeline.bridge_waiting?
...@@ -756,6 +757,8 @@ module Ci ...@@ -756,6 +757,8 @@ module Ci
raise BridgeStatusError unless source_bridge.active? raise BridgeStatusError unless source_bridge.active?
source_bridge.success! source_bridge.success!
rescue => e
Gitlab::ErrorTracking.track_exception(e, pipeline_id: id)
end end
def bridge_triggered? def bridge_triggered?
...@@ -774,6 +777,10 @@ module Ci ...@@ -774,6 +777,10 @@ module Ci
child_pipelines.exists? child_pipelines.exists?
end end
def created_successfully?
persisted? && failure_reason.blank?
end
def detailed_status(current_user) def detailed_status(current_user)
Gitlab::Ci::Status::Pipeline::Factory Gitlab::Ci::Status::Pipeline::Factory
.new(self, current_user) .new(self, current_user)
......
...@@ -188,14 +188,6 @@ class Snippet < ApplicationRecord ...@@ -188,14 +188,6 @@ class Snippet < ApplicationRecord
end end
end end
def self.content_types
[
".rb", ".py", ".pl", ".scala", ".c", ".cpp", ".java",
".haml", ".html", ".sass", ".scss", ".xml", ".php", ".erb",
".js", ".sh", ".coffee", ".yml", ".md"
]
end
def blob def blob
@blob ||= Blob.decorate(SnippetBlob.new(self), self) @blob ||= Blob.decorate(SnippetBlob.new(self), self)
end end
......
...@@ -5,9 +5,19 @@ module Ci ...@@ -5,9 +5,19 @@ module Ci
class CreateCrossProjectPipelineService < ::BaseService class CreateCrossProjectPipelineService < ::BaseService
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
DuplicateDownstreamPipelineError = Class.new(StandardError)
def execute(bridge) def execute(bridge)
@bridge = bridge @bridge = bridge
if bridge.has_downstream_pipeline?
Gitlab::ErrorTracking.track_exception(
DuplicateDownstreamPipelineError.new,
bridge_id: @bridge.id, project_id: @bridge.project_id
)
return
end
pipeline_params = @bridge.downstream_pipeline_params pipeline_params = @bridge.downstream_pipeline_params
target_ref = pipeline_params.dig(:target_revision, :ref) target_ref = pipeline_params.dig(:target_revision, :ref)
...@@ -18,14 +28,32 @@ module Ci ...@@ -18,14 +28,32 @@ module Ci
current_user, current_user,
pipeline_params.fetch(:target_revision)) pipeline_params.fetch(:target_revision))
service.execute( downstream_pipeline = service.execute(
pipeline_params.fetch(:source), pipeline_params[:execute_params]) do |pipeline| pipeline_params.fetch(:source), pipeline_params[:execute_params]) do |pipeline|
pipeline.variables.build(@bridge.downstream_variables) pipeline.variables.build(@bridge.downstream_variables)
end end
downstream_pipeline.tap do |pipeline|
next if Feature.disabled?(:ci_drop_bridge_on_downstream_errors, project, default_enabled: true)
update_bridge_status!(@bridge, pipeline)
end
end end
private private
def update_bridge_status!(bridge, pipeline)
Gitlab::OptimisticLocking.retry_lock(bridge) do |subject|
if pipeline.created_successfully?
# If bridge uses `strategy:depend` we leave it running
# and update the status when the downstream pipeline completes.
subject.success! unless subject.dependent?
else
subject.drop!(:downstream_pipeline_creation_failed)
end
end
end
def ensure_preconditions!(target_ref) def ensure_preconditions!(target_ref)
unless downstream_project_accessible? unless downstream_project_accessible?
@bridge.drop!(:downstream_bridge_project_not_found) @bridge.drop!(:downstream_bridge_project_not_found)
......
---
title: Allow to fork to the same namespace and different path via API call
merge_request: 26062
author:
type: fixed
---
title: Project Snippets API endpoints check feature status
merge_request: 26064
author:
type: performance
---
title: Add support for configuring remote mirrors via API
merge_request: 25825
author: Rajendra Kadam
type: added
---
title: Drop bridge if downstream pipeline has errors
merge_request: 25706
author:
type: fixed
---
title: Add avatar upload support for create and update group APIs
merge_request: 25751
author: Rajendra Kadam
type: added
---
title: Add web_url attribute to API response for Commits
merge_request: 26173
author:
type: added
---
title: Remove unused Snippets#content_types method
merge_request: 26306
author:
type: other
---
title: Send credentials with GraphQL fetch requests
merge_request: 26386
author:
type: fixed
...@@ -79,6 +79,8 @@ From there, you can see the following actions: ...@@ -79,6 +79,8 @@ From there, you can see the following actions:
- Release was added to a project - Release was added to a project
- Release was updated - Release was updated
- Release milestone associations changed - Release milestone associations changed
- Permission to approve merge requests by committers was updated ([introduced](https://gitlab.com/gitlab-org/gitlab/issues/7531) in GitLab 12.9)
- Permission to approve merge requests by authors was updated ([introduced](https://gitlab.com/gitlab-org/gitlab/issues/7531) in GitLab 12.9)
### Instance events **(PREMIUM ONLY)** ### Instance events **(PREMIUM ONLY)**
......
This diff is collapsed.
...@@ -42,7 +42,8 @@ Example response: ...@@ -42,7 +42,8 @@ Example response:
"message": "Replace sanitize with escape once", "message": "Replace sanitize with escape once",
"parent_ids": [ "parent_ids": [
"6104942438c14ec7bd21c6cd5bd995272b3faff6" "6104942438c14ec7bd21c6cd5bd995272b3faff6"
] ],
"web_url": "https://gitlab.example.com/thedude/gitlab-foss/-/commit/ed899a2f4b50b4370feeea94676502b42383c746"
}, },
{ {
"id": "6104942438c14ec7bd21c6cd5bd995272b3faff6", "id": "6104942438c14ec7bd21c6cd5bd995272b3faff6",
...@@ -56,7 +57,8 @@ Example response: ...@@ -56,7 +57,8 @@ Example response:
"message": "Sanitize for network graph", "message": "Sanitize for network graph",
"parent_ids": [ "parent_ids": [
"ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba" "ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba"
] ],
"web_url": "https://gitlab.example.com/thedude/gitlab-foss/-/commit/ed899a2f4b50b4370feeea94676502b42383c746"
} }
] ]
``` ```
...@@ -156,7 +158,8 @@ Example response: ...@@ -156,7 +158,8 @@ Example response:
"deletions": 2, "deletions": 2,
"total": 4 "total": 4
}, },
"status": null "status": null,
"web_url": "https://gitlab.example.com/thedude/gitlab-foss/-/commit/ed899a2f4b50b4370feeea94676502b42383c746"
} }
``` ```
...@@ -235,7 +238,8 @@ Example response: ...@@ -235,7 +238,8 @@ Example response:
"deletions": 10, "deletions": 10,
"total": 25 "total": 25
}, },
"status": "running" "status": "running",
"web_url": "https://gitlab.example.com/thedude/gitlab-foss/-/commit/6104942438c14ec7bd21c6cd5bd995272b3faff6"
} }
``` ```
...@@ -314,7 +318,8 @@ Example response: ...@@ -314,7 +318,8 @@ Example response:
"message": "Feature added\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n", "message": "Feature added\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
"parent_ids": [ "parent_ids": [
"a738f717824ff53aebad8b090c1b79a14f2bd9e8" "a738f717824ff53aebad8b090c1b79a14f2bd9e8"
] ],
"web_url": "https://gitlab.example.com/thedude/gitlab-foss/-/commit/8b090c1b79a14f2bd9e8a738f717824ff53aebad"
} }
``` ```
...@@ -370,7 +375,8 @@ Example response: ...@@ -370,7 +375,8 @@ Example response:
"authored_date":"2018-11-08T15:55:26.000Z", "authored_date":"2018-11-08T15:55:26.000Z",
"committer_name":"Administrator", "committer_name":"Administrator",
"committer_email":"admin@example.com", "committer_email":"admin@example.com",
"committed_date":"2018-11-08T15:55:26.000Z" "committed_date":"2018-11-08T15:55:26.000Z",
"web_url": "https://gitlab.example.com/thedude/gitlab-foss/-/commit/8b090c1b79a14f2bd9e8a738f717824ff53aebad"
} }
``` ```
......
...@@ -492,6 +492,7 @@ Parameters: ...@@ -492,6 +492,7 @@ Parameters:
| `auto_devops_enabled` | boolean | no | Default to Auto DevOps pipeline for all projects within this group. | | `auto_devops_enabled` | boolean | no | Default to Auto DevOps pipeline for all projects within this group. |
| `subgroup_creation_level` | string | no | Allowed to create subgroups. Can be `owner` (Owners), or `maintainer` (Maintainers). | | `subgroup_creation_level` | string | no | Allowed to create subgroups. Can be `owner` (Owners), or `maintainer` (Maintainers). |
| `emails_disabled` | boolean | no | Disable email notifications | | `emails_disabled` | boolean | no | Disable email notifications |
| `avatar` | mixed | no | Image file for avatar of the group. [Introduced in GitLab 12.9](https://gitlab.com/gitlab-org/gitlab/issues/36681) |
| `mentions_disabled` | boolean | no | Disable the capability of a group from getting mentioned | | `mentions_disabled` | boolean | no | Disable the capability of a group from getting mentioned |
| `lfs_enabled` | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group. | | `lfs_enabled` | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group. |
| `request_access_enabled` | boolean | no | Allow users to request member access. | | `request_access_enabled` | boolean | no | Allow users to request member access. |
...@@ -553,6 +554,7 @@ PUT /groups/:id ...@@ -553,6 +554,7 @@ PUT /groups/:id
| `auto_devops_enabled` | boolean | no | Default to Auto DevOps pipeline for all projects within this group. | | `auto_devops_enabled` | boolean | no | Default to Auto DevOps pipeline for all projects within this group. |
| `subgroup_creation_level` | string | no | Allowed to create subgroups. Can be `owner` (Owners), or `maintainer` (Maintainers). | | `subgroup_creation_level` | string | no | Allowed to create subgroups. Can be `owner` (Owners), or `maintainer` (Maintainers). |
| `emails_disabled` | boolean | no | Disable email notifications | | `emails_disabled` | boolean | no | Disable email notifications |
| `avatar` | mixed | no | Image file for avatar of the group. [Introduced in GitLab 12.9](https://gitlab.com/gitlab-org/gitlab/issues/36681) |
| `mentions_disabled` | boolean | no | Disable the capability of a group from getting mentioned | | `mentions_disabled` | boolean | no | Disable the capability of a group from getting mentioned |
| `lfs_enabled` (optional) | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group. | | `lfs_enabled` (optional) | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group. |
| `request_access_enabled` | boolean | no | Allow users to request member access. | | `request_access_enabled` | boolean | no | Allow users to request member access. |
......
...@@ -9,6 +9,10 @@ module API ...@@ -9,6 +9,10 @@ module API
expose :safe_message, as: :message expose :safe_message, as: :message
expose :author_name, :author_email, :authored_date expose :author_name, :author_email, :authored_date
expose :committer_name, :committer_email, :committed_date expose :committer_name, :committer_email, :committed_date
expose :web_url do |commit, _options|
Gitlab::UrlBuilder.build(commit)
end
end end
end end
end end
...@@ -11,6 +11,8 @@ module API ...@@ -11,6 +11,8 @@ module API
optional :visibility, type: String, optional :visibility, type: String,
values: Gitlab::VisibilityLevel.string_values, values: Gitlab::VisibilityLevel.string_values,
desc: 'The visibility of the group' desc: 'The visibility of the group'
# TODO: remove rubocop disable - https://gitlab.com/gitlab-org/gitlab/issues/14960
optional :avatar, type: File, desc: 'Avatar image for the group' # rubocop:disable Scalability/FileUploads
optional :share_with_group_lock, type: Boolean, desc: 'Prevent sharing a project with another group within this group' optional :share_with_group_lock, type: Boolean, desc: 'Prevent sharing a project with another group within this group'
optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users in this group to setup Two-factor authentication' optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users in this group to setup Two-factor authentication'
optional :two_factor_grace_period, type: Integer, desc: 'Time before Two-factor authentication is enforced' optional :two_factor_grace_period, type: Integer, desc: 'Time before Two-factor authentication is enforced'
......
...@@ -5,12 +5,17 @@ module API ...@@ -5,12 +5,17 @@ module API
include PaginationParams include PaginationParams
before { authenticate! } before { authenticate! }
before { check_snippets_enabled }
params do params do
requires :id, type: String, desc: 'The ID of a project' requires :id, type: String, desc: 'The ID of a project'
end end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
helpers do helpers do
def check_snippets_enabled
forbidden! unless user_project.feature_available?(:snippets, current_user)
end
def handle_project_member_errors(errors) def handle_project_member_errors(errors)
if errors[:project_access].any? if errors[:project_access].any?
error!(errors[:project_access], 422) error!(errors[:project_access], 422)
......
...@@ -26,6 +26,26 @@ module API ...@@ -26,6 +26,26 @@ module API
with: Entities::RemoteMirror with: Entities::RemoteMirror
end end
desc 'Create remote mirror for a project' do
success Entities::RemoteMirror
end
params do
requires :url, type: String, desc: 'The URL for a remote mirror'
optional :enabled, type: Boolean, desc: 'Determines if the mirror is enabled'
optional :only_protected_branches, type: Boolean, desc: 'Determines if only protected branches are mirrored'
end
post ':id/remote_mirrors' do
create_params = declared_params(include_missing: false)
new_mirror = user_project.remote_mirrors.create(create_params)
if new_mirror.persisted?
present new_mirror, with: Entities::RemoteMirror
else
render_validation_error!(new_mirror)
end
end
desc 'Update the attributes of a single remote mirror' do desc 'Update the attributes of a single remote mirror' do
success Entities::RemoteMirror success Entities::RemoteMirror
end end
......
...@@ -13,7 +13,8 @@ module Gitlab ...@@ -13,7 +13,8 @@ module Gitlab
end end
def url def url
case object # Objects are sometimes wrapped in a BatchLoader instance
case object.itself
when Commit when Commit
commit_url commit_url
when Issue when Issue
...@@ -33,7 +34,7 @@ module Gitlab ...@@ -33,7 +34,7 @@ module Gitlab
when User when User
user_url(object) user_url(object)
else else
raise NotImplementedError.new("No URL builder defined for #{object.class}") raise NotImplementedError.new("No URL builder defined for #{object.inspect}")
end end
end end
......
...@@ -28,8 +28,8 @@ describe ForkTargetsFinder do ...@@ -28,8 +28,8 @@ describe ForkTargetsFinder do
end end
describe '#execute' do describe '#execute' do
it 'returns all user manageable namespaces except project namespace' do it 'returns all user manageable namespaces' do
expect(finder.execute).to match_array([user.namespace, maintained_group, owned_group]) expect(finder.execute).to match_array([user.namespace, maintained_group, owned_group, project.namespace])
end end
end end
end end
...@@ -12,7 +12,8 @@ ...@@ -12,7 +12,8 @@
"authored_date", "authored_date",
"committer_name", "committer_name",
"committer_email", "committer_email",
"committed_date" "committed_date",
"web_url"
], ],
"properties" : { "properties" : {
"id": { "type": ["string", "null"] }, "id": { "type": ["string", "null"] },
...@@ -32,6 +33,7 @@ ...@@ -32,6 +33,7 @@
"authored_date": { "type": "date" }, "authored_date": { "type": "date" },
"committer_name": { "type": "string" }, "committer_name": { "type": "string" },
"committer_email": { "type": "string" }, "committer_email": { "type": "string" },
"committed_date": { "type": "date" } "committed_date": { "type": "date" },
"web_url": { "type": "string" }
} }
} }
...@@ -14,6 +14,18 @@ describe Gitlab::UrlBuilder do ...@@ -14,6 +14,18 @@ describe Gitlab::UrlBuilder do
end end
end end
context 'when passing a batch loaded Commit' do
it 'returns a proper URL' do
commit = BatchLoader.for(:commit).batch do |batch, loader|
batch.each { |commit| loader.call(:commit, build_stubbed(:commit)) }
end
url = described_class.build(commit)
expect(url).to eq "#{Settings.gitlab['url']}/#{commit.project.full_path}/-/commit/#{commit.id}"
end
end
context 'when passing an Issue' do context 'when passing an Issue' do
it 'returns a proper URL' do it 'returns a proper URL' do
issue = build_stubbed(:issue, iid: 42) issue = build_stubbed(:issue, iid: 42)
...@@ -160,7 +172,7 @@ describe Gitlab::UrlBuilder do ...@@ -160,7 +172,7 @@ describe Gitlab::UrlBuilder do
project = build_stubbed(:project) project = build_stubbed(:project)
expect { described_class.build(project) } expect { described_class.build(project) }
.to raise_error(NotImplementedError, 'No URL builder defined for Project') .to raise_error(NotImplementedError, "No URL builder defined for #{project.inspect}")
end end
end end
end end
......
...@@ -2813,6 +2813,30 @@ describe Ci::Pipeline, :mailer do ...@@ -2813,6 +2813,30 @@ describe Ci::Pipeline, :mailer do
end end
end end
describe '#created_successfully?' do
subject { pipeline.created_successfully? }
context 'when pipeline is not persisted' do
let(:pipeline) { build(:ci_pipeline) }
it { is_expected.to be_falsey }
end
context 'when pipeline is persisted' do
context 'when pipeline has failure reasons' do
let(:pipeline) { create(:ci_pipeline, failure_reason: :config_error) }
it { is_expected.to be_falsey }
end
context 'when pipeline has no failure reasons' do
let(:pipeline) { create(:ci_pipeline, failure_reason: nil) }
it { is_expected.to be_truthy }
end
end
end
describe '#parent_pipeline' do describe '#parent_pipeline' do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) } let(:pipeline) { create(:ci_pipeline, project: project) }
...@@ -2960,8 +2984,7 @@ describe Ci::Pipeline, :mailer do ...@@ -2960,8 +2984,7 @@ describe Ci::Pipeline, :mailer do
it 'can not update bridge status if is not active' do it 'can not update bridge status if is not active' do
bridge.success! bridge.success!
expect { pipeline.update_bridge_status! } expect { pipeline.update_bridge_status! }.not_to change { bridge.status }
.to raise_error Ci::Pipeline::BridgeStatusError
end end
end end
end end
...@@ -2992,9 +3015,12 @@ describe Ci::Pipeline, :mailer do ...@@ -2992,9 +3015,12 @@ describe Ci::Pipeline, :mailer do
end end
describe '#update_bridge_status!' do describe '#update_bridge_status!' do
it 'can not update upstream job status' do it 'tracks an ArgumentError and does not update upstream job status' do
expect { pipeline.update_bridge_status! } expect(Gitlab::ErrorTracking)
.to raise_error ArgumentError .to receive(:track_exception)
.with(instance_of(ArgumentError), pipeline_id: pipeline.id)
pipeline.update_bridge_status!
end end
end end
end end
......
...@@ -21,6 +21,47 @@ describe API::Groups do ...@@ -21,6 +21,47 @@ describe API::Groups do
group2.add_owner(user2) group2.add_owner(user2)
end end
shared_examples 'group avatar upload' do
context 'when valid' do
let(:file_path) { 'spec/fixtures/banana_sample.gif' }
it 'returns avatar url in response' do
make_upload_request
group_id = json_response['id']
expect(json_response['avatar_url']).to eq('http://localhost/uploads/'\
'-/system/group/avatar/'\
"#{group_id}/banana_sample.gif")
end
end
context 'when invalid' do
shared_examples 'invalid file upload request' do
it 'returns 400' do
make_upload_request
expect(response).to have_gitlab_http_status(:bad_request)
expect(response.message).to eq('Bad Request')
expect(json_response['message'].to_s).to match(/#{message}/)
end
end
context 'when file format is not supported' do
let(:file_path) { 'spec/fixtures/doc_sample.txt' }
let(:message) { 'file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff, ico' }
it_behaves_like 'invalid file upload request'
end
context 'when file format is not supported' do
let(:file_path) { 'spec/fixtures/big-image.png' }
let(:message) { 'is too big' }
it_behaves_like 'invalid file upload request'
end
end
end
describe "GET /groups" do describe "GET /groups" do
context "when unauthenticated" do context "when unauthenticated" do
it "returns public groups" do it "returns public groups" do
...@@ -539,6 +580,15 @@ describe API::Groups do ...@@ -539,6 +580,15 @@ describe API::Groups do
describe 'PUT /groups/:id' do describe 'PUT /groups/:id' do
let(:new_group_name) { 'New Group'} let(:new_group_name) { 'New Group'}
it_behaves_like 'group avatar upload' do
def make_upload_request
group_param = {
avatar: fixture_file_upload(file_path)
}
put api("/groups/#{group1.id}", user1), params: group_param
end
end
context 'when authenticated as the group owner' do context 'when authenticated as the group owner' do
it 'updates the group' do it 'updates the group' do
put api("/groups/#{group1.id}", user1), params: { put api("/groups/#{group1.id}", user1), params: {
...@@ -940,6 +990,16 @@ describe API::Groups do ...@@ -940,6 +990,16 @@ describe API::Groups do
end end
describe "POST /groups" do describe "POST /groups" do
it_behaves_like 'group avatar upload' do
def make_upload_request
params = attributes_for_group_api(request_access_enabled: false).tap do |attrs|
attrs[:avatar] = fixture_file_upload(file_path)
end
post api("/groups", user3), params: params
end
end
context "when authenticated as user without group permissions" do context "when authenticated as user without group permissions" do
it "does not create group" do it "does not create group" do
group = attributes_for_group_api group = attributes_for_group_api
......
...@@ -1150,12 +1150,16 @@ describe API::MergeRequests do ...@@ -1150,12 +1150,16 @@ describe API::MergeRequests do
describe 'POST /projects/:id/merge_requests/:merge_request_iid/pipelines' do describe 'POST /projects/:id/merge_requests/:merge_request_iid/pipelines' do
before do before do
stub_ci_pipeline_yaml_file(YAML.dump({ stub_ci_pipeline_yaml_file(ci_yaml)
end
let(:ci_yaml) do
YAML.dump({
rspec: { rspec: {
script: 'ls', script: 'ls',
only: ['merge_requests'] only: ['merge_requests']
} }
})) })
end end
let(:project) do let(:project) do
...@@ -1208,6 +1212,18 @@ describe API::MergeRequests do ...@@ -1208,6 +1212,18 @@ describe API::MergeRequests do
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
end end
context 'when the .gitlab-ci.yml file is invalid' do
let(:ci_yaml) { 'invalid yaml file' }
it 'creates a failed pipeline' do
expect { request }.to change(Ci::Pipeline, :count).by(1)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_a Hash
expect(merge_request.pipelines_for_merge_request.last).to be_failed
expect(merge_request.pipelines_for_merge_request.last).to be_config_error
end
end
end end
describe 'POST /projects/:id/merge_requests' do describe 'POST /projects/:id/merge_requests' do
......
...@@ -6,6 +6,12 @@ describe API::ProjectSnippets do ...@@ -6,6 +6,12 @@ describe API::ProjectSnippets do
let_it_be(:project) { create(:project, :public) } let_it_be(:project) { create(:project, :public) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) } let_it_be(:admin) { create(:admin) }
let_it_be(:project_no_snippets) { create(:project, :snippets_disabled) }
before do
project_no_snippets.add_developer(admin)
project_no_snippets.add_developer(user)
end
describe "GET /projects/:project_id/snippets/:id/user_agent_detail" do describe "GET /projects/:project_id/snippets/:id/user_agent_detail" do
let(:snippet) { create(:project_snippet, :public, project: project) } let(:snippet) { create(:project_snippet, :public, project: project) }
...@@ -32,6 +38,12 @@ describe API::ProjectSnippets do ...@@ -32,6 +38,12 @@ describe API::ProjectSnippets do
expect(response).to have_gitlab_http_status(:forbidden) expect(response).to have_gitlab_http_status(:forbidden)
end end
context 'with snippets disabled' do
it_behaves_like '403 response' do
let(:request) { get api("/projects/#{project_no_snippets.id}/snippets/123/user_agent_detail", admin) }
end
end
end end
describe 'GET /projects/:project_id/snippets/' do describe 'GET /projects/:project_id/snippets/' do
...@@ -63,6 +75,12 @@ describe API::ProjectSnippets do ...@@ -63,6 +75,12 @@ describe API::ProjectSnippets do
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.size).to eq(0) expect(json_response.size).to eq(0)
end end
context 'with snippets disabled' do
it_behaves_like '403 response' do
let(:request) { get api("/projects/#{project_no_snippets.id}/snippets", user) }
end
end
end end
describe 'GET /projects/:project_id/snippets/:id' do describe 'GET /projects/:project_id/snippets/:id' do
...@@ -85,6 +103,12 @@ describe API::ProjectSnippets do ...@@ -85,6 +103,12 @@ describe API::ProjectSnippets do
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 Not found') expect(json_response['message']).to eq('404 Not found')
end end
context 'with snippets disabled' do
it_behaves_like '403 response' do
let(:request) { get api("/projects/#{project_no_snippets.id}/snippets/123", user) }
end
end
end end
describe 'POST /projects/:project_id/snippets/' do describe 'POST /projects/:project_id/snippets/' do
...@@ -244,11 +268,17 @@ describe API::ProjectSnippets do ...@@ -244,11 +268,17 @@ describe API::ProjectSnippets do
end end
end end
end end
context 'with snippets disabled' do
it_behaves_like '403 response' do
let(:request) { post api("/projects/#{project_no_snippets.id}/snippets", user), params: params }
end
end
end end
describe 'PUT /projects/:project_id/snippets/:id/' do describe 'PUT /projects/:project_id/snippets/:id/' do
let(:visibility_level) { Snippet::PUBLIC } let(:visibility_level) { Snippet::PUBLIC }
let(:snippet) { create(:project_snippet, author: admin, visibility_level: visibility_level) } let(:snippet) { create(:project_snippet, author: admin, visibility_level: visibility_level, project: project) }
it 'updates snippet' do it 'updates snippet' do
new_content = 'New content' new_content = 'New content'
...@@ -354,10 +384,16 @@ describe API::ProjectSnippets do ...@@ -354,10 +384,16 @@ describe API::ProjectSnippets do
end end
end end
end end
context 'with snippets disabled' do
it_behaves_like '403 response' do
let(:request) { put api("/projects/#{project_no_snippets.id}/snippets/123", admin), params: { description: 'foo' } }
end
end
end end
describe 'DELETE /projects/:project_id/snippets/:id/' do describe 'DELETE /projects/:project_id/snippets/:id/' do
let(:snippet) { create(:project_snippet, author: admin) } let(:snippet) { create(:project_snippet, author: admin, project: project) }
it 'deletes snippet' do it 'deletes snippet' do
delete api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin) delete api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin)
...@@ -375,10 +411,16 @@ describe API::ProjectSnippets do ...@@ -375,10 +411,16 @@ describe API::ProjectSnippets do
it_behaves_like '412 response' do it_behaves_like '412 response' do
let(:request) { api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin) } let(:request) { api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin) }
end end
context 'with snippets disabled' do
it_behaves_like '403 response' do
let(:request) { delete api("/projects/#{project_no_snippets.id}/snippets/123", admin) }
end
end
end end
describe 'GET /projects/:project_id/snippets/:id/raw' do describe 'GET /projects/:project_id/snippets/:id/raw' do
let(:snippet) { create(:project_snippet, author: admin) } let(:snippet) { create(:project_snippet, author: admin, project: project) }
it 'returns raw text' do it 'returns raw text' do
get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", admin) get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", admin)
...@@ -394,5 +436,11 @@ describe API::ProjectSnippets do ...@@ -394,5 +436,11 @@ describe API::ProjectSnippets do
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 Snippet Not Found') expect(json_response['message']).to eq('404 Snippet Not Found')
end end
context 'with snippets disabled' do
it_behaves_like '403 response' do
let(:request) { get api("/projects/#{project_no_snippets.id}/snippets/123/raw", admin) }
end
end
end end
end end
...@@ -2935,6 +2935,26 @@ describe API::Projects do ...@@ -2935,6 +2935,26 @@ describe API::Projects do
expect(response).to have_gitlab_http_status(:conflict) expect(response).to have_gitlab_http_status(:conflict)
expect(json_response['message']['name']).to eq(['has already been taken']) expect(json_response['message']['name']).to eq(['has already been taken'])
end end
it 'forks to the same namespace with alternative path and name' do
post api("/projects/#{project.id}/fork", user), params: { path: 'path_2', name: 'name_2' }
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq('name_2')
expect(json_response['path']).to eq('path_2')
expect(json_response['owner']['id']).to eq(user.id)
expect(json_response['namespace']['id']).to eq(user.namespace.id)
expect(json_response['forked_from_project']['id']).to eq(project.id)
expect(json_response['import_status']).to eq('scheduled')
end
it 'fails to fork to the same namespace without alternative path and name' do
post api("/projects/#{project.id}/fork", user)
expect(response).to have_gitlab_http_status(:conflict)
expect(json_response['message']['path']).to eq(['has already been taken'])
expect(json_response['message']['name']).to eq(['has already been taken'])
end
end end
context 'when unauthenticated' do context 'when unauthenticated' do
......
...@@ -39,6 +39,54 @@ describe API::RemoteMirrors do ...@@ -39,6 +39,54 @@ describe API::RemoteMirrors do
end end
end end
describe 'POST /projects/:id/remote_mirrors' do
let(:route) { "/projects/#{project.id}/remote_mirrors" }
shared_examples 'creates a remote mirror' do
it 'creates a remote mirror and returns reponse' do
project.add_maintainer(user)
post api(route, user), params: params
enabled = params.fetch(:enabled, false)
expect(response).to have_gitlab_http_status(:success)
expect(response).to match_response_schema('remote_mirror')
expect(json_response['enabled']).to eq(enabled)
end
end
it 'requires `admin_remote_mirror` permission' do
post api(route, developer)
expect(response).to have_gitlab_http_status(:unauthorized)
end
context 'creates a remote mirror' do
context 'disabled by default' do
let(:params) { { url: 'https://foo:bar@test.com' } }
it_behaves_like 'creates a remote mirror'
end
context 'enabled' do
let(:params) { { url: 'https://foo:bar@test.com', enabled: true } }
it_behaves_like 'creates a remote mirror'
end
end
it 'returns error if url is invalid' do
project.add_maintainer(user)
post api(route, user), params: {
url: 'ftp://foo:bar@test.com'
}
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']['url']).to eq(["is blocked: Only allowed schemes are ssh, git, http, https"])
end
end
describe 'PUT /projects/:id/remote_mirrors/:mirror_id' do describe 'PUT /projects/:id/remote_mirrors/:mirror_id' do
let(:route) { ->(id) { "/projects/#{project.id}/remote_mirrors/#{id}" } } let(:route) { ->(id) { "/projects/#{project.id}/remote_mirrors/#{id}" } }
let(:mirror) { project.remote_mirrors.first } let(:mirror) { project.remote_mirrors.first }
......
...@@ -116,6 +116,28 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do ...@@ -116,6 +116,28 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do
expect(bridge.reload).to be_success expect(bridge.reload).to be_success
end end
context 'when bridge job has already any downstream pipelines' do
before do
bridge.sourced_pipelines.create!(
source_pipeline: bridge.pipeline,
source_project: bridge.project,
project: bridge.project,
pipeline: create(:ci_pipeline, project: bridge.project)
)
end
it 'logs an error and exits' do
expect(Gitlab::ErrorTracking)
.to receive(:track_exception)
.with(
instance_of(Ci::CreateCrossProjectPipelineService::DuplicateDownstreamPipelineError),
bridge_id: bridge.id, project_id: bridge.project.id)
.and_call_original
expect(Ci::CreatePipelineService).not_to receive(:new)
expect(service.execute(bridge)).to be_nil
end
end
context 'when target ref is not specified' do context 'when target ref is not specified' do
let(:trigger) do let(:trigger) do
{ trigger: { project: downstream_project.full_path } } { trigger: { project: downstream_project.full_path } }
...@@ -149,13 +171,11 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do ...@@ -149,13 +171,11 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do
expect(pipeline.source_bridge).to be_a ::Ci::Bridge expect(pipeline.source_bridge).to be_a ::Ci::Bridge
end end
it 'does not update bridge status when downstream pipeline gets processed' do it 'updates the bridge status when downstream pipeline gets processed' do
pipeline = service.execute(bridge) pipeline = service.execute(bridge)
expect(pipeline.reload).to be_failed expect(pipeline.reload).to be_failed
# TODO: This should change to failed once #198354 gets fixed. expect(bridge.reload).to be_failed
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25706
expect(bridge.reload).to be_pending
end end
end end
...@@ -242,6 +262,22 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do ...@@ -242,6 +262,22 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do
it_behaves_like 'creates a child pipeline' it_behaves_like 'creates a child pipeline'
it 'updates the bridge job to success' do
expect { service.execute(bridge) }.to change { bridge.status }.to 'success'
end
context 'when bridge uses "depend" strategy' do
let(:trigger) do
{
trigger: { include: 'child-pipeline.yml', strategy: 'depend' }
}
end
it 'does not update the bridge job status' do
expect { service.execute(bridge) }.not_to change { bridge.status }
end
end
context 'when latest sha for the ref changed in the meantime' do context 'when latest sha for the ref changed in the meantime' do
before do before do
upstream_project.repository.create_file( upstream_project.repository.create_file(
...@@ -298,6 +334,34 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do ...@@ -298,6 +334,34 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do
end end
end end
context 'when downstream pipeline creation errors out' do
let(:stub_config) { false }
before do
stub_ci_pipeline_yaml_file(YAML.dump(invalid: { yaml: 'error' }))
end
it 'creates only one new pipeline' do
expect { service.execute(bridge) }
.to change { Ci::Pipeline.count }.by(1)
end
it 'creates a new pipeline in the downstream project' do
pipeline = service.execute(bridge)
expect(pipeline.user).to eq bridge.user
expect(pipeline.project).to eq downstream_project
end
it 'drops the bridge' do
pipeline = service.execute(bridge)
expect(pipeline.reload).to be_failed
expect(bridge.reload).to be_failed
expect(bridge.failure_reason).to eq('downstream_pipeline_creation_failed')
end
end
context 'when bridge job has YAML variables defined' do context 'when bridge job has YAML variables defined' do
before do before do
bridge.yaml_variables = [{ key: 'BRIDGE', value: 'var', public: true }] bridge.yaml_variables = [{ key: 'BRIDGE', value: 'var', public: true }]
......
...@@ -129,6 +129,21 @@ describe Git::BranchPushService, services: true do ...@@ -129,6 +129,21 @@ describe Git::BranchPushService, services: true do
end end
end end
end end
context 'when .gitlab-ci.yml file is invalid' do
before do
stub_ci_pipeline_yaml_file('invalid yaml file')
end
it 'persists an error pipeline' do
expect { subject }.to change { Ci::Pipeline.count }
pipeline = Ci::Pipeline.last
expect(pipeline).to be_push
expect(pipeline).to be_failed
expect(pipeline).to be_config_error
end
end
end end
describe "Updates merge requests" do describe "Updates merge requests" do
......
...@@ -177,18 +177,18 @@ describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do ...@@ -177,18 +177,18 @@ describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
describe 'Pipelines for merge requests' do describe 'Pipelines for merge requests' do
before do before do
stub_ci_pipeline_yaml_file(YAML.dump(config)) stub_ci_pipeline_yaml_file(config)
end end
context "when .gitlab-ci.yml has merge_requests keywords" do context "when .gitlab-ci.yml has merge_requests keywords" do
let(:config) do let(:config) do
{ YAML.dump({
test: { test: {
stage: 'test', stage: 'test',
script: 'echo', script: 'echo',
only: ['merge_requests'] only: ['merge_requests']
} }
} })
end end
it 'creates a detached merge request pipeline and sets it as a head pipeline' do it 'creates a detached merge request pipeline and sets it as a head pipeline' do
...@@ -269,12 +269,12 @@ describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do ...@@ -269,12 +269,12 @@ describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
context "when .gitlab-ci.yml does not have merge_requests keywords" do context "when .gitlab-ci.yml does not have merge_requests keywords" do
let(:config) do let(:config) do
{ YAML.dump({
test: { test: {
stage: 'test', stage: 'test',
script: 'echo' script: 'echo'
} }
} })
end end
it 'does not create a detached merge request pipeline' do it 'does not create a detached merge request pipeline' do
...@@ -284,6 +284,19 @@ describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do ...@@ -284,6 +284,19 @@ describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
expect(merge_request.pipelines_for_merge_request.count).to eq(0) expect(merge_request.pipelines_for_merge_request.count).to eq(0)
end end
end end
context 'when .gitlab-ci.yml is invalid' do
let(:config) { 'invalid yaml file' }
it 'persists a pipeline with config error' do
expect(merge_request).to be_persisted
merge_request.reload
expect(merge_request.pipelines_for_merge_request.count).to eq(1)
expect(merge_request.pipelines_for_merge_request.last).to be_failed
expect(merge_request.pipelines_for_merge_request.last).to be_config_error
end
end
end end
it 'increments the usage data counter of create event' do it 'increments the usage data counter of create event' do
......
...@@ -148,7 +148,7 @@ describe MergeRequests::RefreshService do ...@@ -148,7 +148,7 @@ describe MergeRequests::RefreshService do
describe 'Pipelines for merge requests' do describe 'Pipelines for merge requests' do
before do before do
stub_ci_pipeline_yaml_file(YAML.dump(config)) stub_ci_pipeline_yaml_file(config)
end end
subject { service.new(project, @user).execute(@oldrev, @newrev, ref) } subject { service.new(project, @user).execute(@oldrev, @newrev, ref) }
...@@ -158,13 +158,13 @@ describe MergeRequests::RefreshService do ...@@ -158,13 +158,13 @@ describe MergeRequests::RefreshService do
context "when .gitlab-ci.yml has merge_requests keywords" do context "when .gitlab-ci.yml has merge_requests keywords" do
let(:config) do let(:config) do
{ YAML.dump({
test: { test: {
stage: 'test', stage: 'test',
script: 'echo', script: 'echo',
only: ['merge_requests'] only: ['merge_requests']
} }
} })
end end
it 'create detached merge request pipeline with commits' do it 'create detached merge request pipeline with commits' do
...@@ -255,16 +255,28 @@ describe MergeRequests::RefreshService do ...@@ -255,16 +255,28 @@ describe MergeRequests::RefreshService do
end.not_to change { @merge_request.pipelines_for_merge_request.count } end.not_to change { @merge_request.pipelines_for_merge_request.count }
end end
end end
context 'when the pipeline should be skipped' do
it 'saves a skipped detached merge request pipeline' do
project.repository.create_file(@user, 'new-file.txt', 'A new file',
message: '[skip ci] This is a test',
branch_name: 'master')
expect { subject }
.to change { @merge_request.pipelines_for_merge_request.count }.by(1)
expect(@merge_request.pipelines_for_merge_request.last).to be_skipped
end
end
end end
context "when .gitlab-ci.yml does not have merge_requests keywords" do context "when .gitlab-ci.yml does not have merge_requests keywords" do
let(:config) do let(:config) do
{ YAML.dump({
test: { test: {
stage: 'test', stage: 'test',
script: 'echo' script: 'echo'
} }
} })
end end
it 'does not create a detached merge request pipeline' do it 'does not create a detached merge request pipeline' do
...@@ -272,6 +284,40 @@ describe MergeRequests::RefreshService do ...@@ -272,6 +284,40 @@ describe MergeRequests::RefreshService do
.not_to change { @merge_request.pipelines_for_merge_request.count } .not_to change { @merge_request.pipelines_for_merge_request.count }
end end
end end
context 'when .gitlab-ci.yml is invalid' do
let(:config) { 'invalid yaml file' }
it 'persists a pipeline with config error' do
expect { subject }
.to change { @merge_request.pipelines_for_merge_request.count }.by(1)
expect(@merge_request.pipelines_for_merge_request.last).to be_failed
expect(@merge_request.pipelines_for_merge_request.last).to be_config_error
end
end
context 'when .gitlab-ci.yml file is valid but has a logical error' do
let(:config) do
YAML.dump({
build: {
script: 'echo "Valid yaml syntax, but..."',
only: ['master']
},
test: {
script: 'echo "... I depend on build, which does not run."',
only: ['merge_request'],
needs: ['build']
}
})
end
it 'persists a pipeline with config error' do
expect { subject }
.to change { @merge_request.pipelines_for_merge_request.count }.by(1)
expect(@merge_request.pipelines_for_merge_request.last).to be_failed
expect(@merge_request.pipelines_for_merge_request.last).to be_config_error
end
end
end end
context 'push to origin repo source branch' do context 'push to origin repo source branch' do
......
...@@ -796,10 +796,10 @@ ...@@ -796,10 +796,10 @@
dependencies: dependencies:
vue-eslint-parser "^7.0.0" vue-eslint-parser "^7.0.0"
"@gitlab/svgs@^1.104.0": "@gitlab/svgs@^1.105.0":
version "1.104.0" version "1.105.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.104.0.tgz#ebbf99788d74b7224f116f1c0040fa0c90034d99" resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.105.0.tgz#9686f8696594a5f22de11af2b81fdcceb715f4f2"
integrity sha512-lWg/EzxFdbx4YIdDWB2p5ag6Cna78AYGET8nXQYXYwd21/U3wKXKL7vsGR4kOxe1goA9ZAYG9eY+MK7cf+X2cA== integrity sha512-2wzZXe2b7DnGyL7FTbPq0dSpk+gjkq4SBTNtMrqdwX2qaM+XJB50XaMm17kdY5V1bBkMgbc7JJ2vgbLxhS/CkQ==
"@gitlab/ui@^9.20.0": "@gitlab/ui@^9.20.0":
version "9.20.0" version "9.20.0"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment