Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorZ.J. van de Weg <git@zjvandeweg.nl>2017-03-02 12:22:56 +0300
committerZ.J. van de Weg <git@zjvandeweg.nl>2017-03-02 12:22:56 +0300
commitddfd09c09cb904529df567e5606d76c467ed856f (patch)
treed17a48cf08e580cac866417700ea1ffe74c48bd7 /lib
parent52c4a7866ed010d8db67e5ca976d8c73d4084784 (diff)
parent3b3f0fab452fb9cbe4fbe7f75112bef3a7f9d039 (diff)
Merge branch 'master' into zj-create-mattermost-team
Diffstat (limited to 'lib')
-rw-r--r--lib/api/api.rb11
-rw-r--r--lib/api/api_guard.rb11
-rw-r--r--lib/api/award_emoji.rb1
-rw-r--r--lib/api/boards.rb4
-rw-r--r--lib/api/branches.rb8
-rw-r--r--lib/api/broadcast_messages.rb2
-rw-r--r--lib/api/commits.rb7
-rw-r--r--lib/api/entities.rb12
-rw-r--r--lib/api/environments.rb19
-rw-r--r--lib/api/files.rb5
-rw-r--r--lib/api/groups.rb16
-rw-r--r--lib/api/helpers.rb4
-rw-r--r--lib/api/helpers/runner.rb23
-rw-r--r--lib/api/internal.rb12
-rw-r--r--lib/api/labels.rb4
-rw-r--r--lib/api/members.rb27
-rw-r--r--lib/api/merge_requests.rb10
-rw-r--r--lib/api/notes.rb2
-rw-r--r--lib/api/project_hooks.rb9
-rw-r--r--lib/api/projects.rb10
-rw-r--r--lib/api/runner.rb52
-rw-r--r--lib/api/runners.rb5
-rw-r--r--lib/api/services.rb19
-rw-r--r--lib/api/settings.rb7
-rw-r--r--lib/api/snippets.rb3
-rw-r--r--lib/api/system_hooks.rb2
-rw-r--r--lib/api/tags.rb6
-rw-r--r--lib/api/triggers.rb13
-rw-r--r--lib/api/users.rb4
-rw-r--r--lib/api/v3/award_emoji.rb59
-rw-r--r--lib/api/v3/boards.rb21
-rw-r--r--lib/api/v3/branches.rb27
-rw-r--r--lib/api/v3/broadcast_messages.rb31
-rw-r--r--lib/api/v3/commits.rb7
-rw-r--r--lib/api/v3/entities.rb80
-rw-r--r--lib/api/v3/environments.rb29
-rw-r--r--lib/api/v3/groups.rb38
-rw-r--r--lib/api/v3/issues.rb2
-rw-r--r--lib/api/v3/labels.rb15
-rw-r--r--lib/api/v3/members.rb1
-rw-r--r--lib/api/v3/merge_requests.rb30
-rw-r--r--lib/api/v3/project_snippets.rb2
-rw-r--r--lib/api/v3/projects.rb67
-rw-r--r--lib/api/v3/runners.rb65
-rw-r--r--lib/api/v3/services.rb573
-rw-r--r--lib/api/v3/system_hooks.rb13
-rw-r--r--lib/api/v3/tags.rb20
-rw-r--r--lib/api/v3/todos.rb2
-rw-r--r--lib/api/v3/triggers.rb30
-rw-r--r--lib/api/v3/users.rb32
-rw-r--r--lib/api/v3/variables.rb29
-rw-r--r--lib/api/variables.rb6
-rw-r--r--lib/banzai/filter/image_link_filter.rb9
-rw-r--r--lib/bitbucket/error/unauthorized.rb3
-rw-r--r--lib/ci/api/builds.rb6
-rw-r--r--lib/ci/api/runners.rb2
-rw-r--r--lib/ci/gitlab_ci_yaml_processor.rb2
-rw-r--r--lib/extracts_path.rb2
-rw-r--r--lib/gitlab/access.rb2
-rw-r--r--lib/gitlab/auth.rb2
-rw-r--r--lib/gitlab/ci/build/artifacts/metadata.rb2
-rw-r--r--lib/gitlab/ci/config/entry/factory.rb2
-rw-r--r--lib/gitlab/ci/config/entry/node.rb2
-rw-r--r--lib/gitlab/ci/config/loader.rb2
-rw-r--r--lib/gitlab/conflict/file.rb3
-rw-r--r--lib/gitlab/conflict/file_collection.rb3
-rw-r--r--lib/gitlab/conflict/parser.rb22
-rw-r--r--lib/gitlab/conflict/resolution_error.rb3
-rw-r--r--lib/gitlab/email/receiver.rb26
-rw-r--r--lib/gitlab/git/blob.rb157
-rw-r--r--lib/gitlab/git/diff.rb2
-rw-r--r--lib/gitlab/git/index.rb126
-rw-r--r--lib/gitlab/git/repository.rb97
-rw-r--r--lib/gitlab/gitaly_client.rb29
-rw-r--r--lib/gitlab/gitaly_client/notifications.rb17
-rw-r--r--lib/gitlab/gon_helper.rb2
-rw-r--r--lib/gitlab/import_export.rb2
-rw-r--r--lib/gitlab/import_export/error.rb2
-rw-r--r--lib/gitlab/middleware/webpack_proxy.rb12
-rw-r--r--lib/gitlab/o_auth/user.rb2
-rw-r--r--lib/gitlab/regex.rb11
-rw-r--r--lib/gitlab/route_map.rb2
-rw-r--r--lib/gitlab/serializer/pagination.rb2
-rw-r--r--lib/gitlab/shell.rb2
-rw-r--r--lib/gitlab/template/finders/repo_template_finder.rb2
-rw-r--r--lib/gitlab/update_path_error.rb2
-rw-r--r--lib/mattermost/client.rb2
-rw-r--r--lib/mattermost/error.rb2
-rw-r--r--lib/mattermost/session.rb2
89 files changed, 1580 insertions, 475 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 1803387bb8c..b27ac3f1d15 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -5,11 +5,15 @@ module API
version %w(v3 v4), using: :path
version 'v3', using: :path do
+ mount ::API::V3::AwardEmoji
mount ::API::V3::Boards
mount ::API::V3::Branches
+ mount ::API::V3::BroadcastMessages
mount ::API::V3::Commits
mount ::API::V3::DeployKeys
+ mount ::API::V3::Environments
mount ::API::V3::Files
+ mount ::API::V3::Groups
mount ::API::V3::Issues
mount ::API::V3::Labels
mount ::API::V3::Members
@@ -20,12 +24,16 @@ module API
mount ::API::V3::Projects
mount ::API::V3::ProjectSnippets
mount ::API::V3::Repositories
+ mount ::API::V3::Runners
+ mount ::API::V3::Services
mount ::API::V3::Subscriptions
mount ::API::V3::SystemHooks
mount ::API::V3::Tags
- mount ::API::V3::Todos
mount ::API::V3::Templates
+ mount ::API::V3::Todos
+ mount ::API::V3::Triggers
mount ::API::V3::Users
+ mount ::API::V3::Variables
end
before { allow_access_with_scope :api }
@@ -90,6 +98,7 @@ module API
mount ::API::Projects
mount ::API::ProjectSnippets
mount ::API::Repositories
+ mount ::API::Runner
mount ::API::Runners
mount ::API::Services
mount ::API::Session
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index c11f8529183..409cb5b924f 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -160,13 +160,10 @@ module API
# Exceptions
#
- class MissingTokenError < StandardError; end
-
- class TokenNotFoundError < StandardError; end
-
- class ExpiredError < StandardError; end
-
- class RevokedError < StandardError; end
+ MissingTokenError = Class.new(StandardError)
+ TokenNotFoundError = Class.new(StandardError)
+ ExpiredError = Class.new(StandardError)
+ RevokedError = Class.new(StandardError)
class InsufficientScopeError < StandardError
attr_reader :scopes
diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb
index 301271118d4..07a1bcdbe18 100644
--- a/lib/api/award_emoji.rb
+++ b/lib/api/award_emoji.rb
@@ -83,7 +83,6 @@ module API
unauthorized! unless award.user == current_user || current_user.admin?
award.destroy
- present award, with: Entities::AwardEmoji
end
end
end
diff --git a/lib/api/boards.rb b/lib/api/boards.rb
index f4226e5a89d..b6843c1b6af 100644
--- a/lib/api/boards.rb
+++ b/lib/api/boards.rb
@@ -127,9 +127,7 @@ module API
service = ::Boards::Lists::DestroyService.new(user_project, current_user)
- if service.execute(list)
- present list, with: Entities::List
- else
+ unless service.execute(list)
render_api_error!({ error: 'List could not be deleted!' }, 400)
end
end
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index c65de90cca2..73a7e939627 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -124,11 +124,7 @@ module API
result = DeleteBranchService.new(user_project, current_user).
execute(params[:branch])
- if result[:status] == :success
- {
- branch: params[:branch]
- }
- else
+ if result[:status] != :success
render_api_error!(result[:message], result[:return_code])
end
end
@@ -137,7 +133,7 @@ module API
delete ":id/repository/merged_branches" do
DeleteMergedBranchesService.new(user_project, current_user).async_execute
- status(200)
+ accepted!
end
end
end
diff --git a/lib/api/broadcast_messages.rb b/lib/api/broadcast_messages.rb
index 1217002bf8e..395c401203c 100644
--- a/lib/api/broadcast_messages.rb
+++ b/lib/api/broadcast_messages.rb
@@ -91,7 +91,7 @@ module API
delete ':id' do
message = find_message
- present message.destroy, with: Entities::BroadcastMessage
+ message.destroy
end
end
end
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 9ce396d4660..fd03e92264d 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -52,13 +52,6 @@ module API
attrs = declared_params.merge(start_branch: declared_params[:branch], target_branch: declared_params[:branch])
- attrs[:actions].map! do |action|
- action[:action] = action[:action].to_sym
- action[:file_path].slice!(0) if action[:file_path] && action[:file_path].start_with?('/')
- action[:previous_path].slice!(0) if action[:previous_path] && action[:previous_path].start_with?('/')
- action
- end
-
result = ::Files::MultiService.new(user_project, current_user, attrs).execute
if result[:status] == :success
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 85aa6932f81..9dccaff369e 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -98,7 +98,7 @@ module API
expose :shared_with_groups do |project, options|
SharedGroup.represent(project.project_group_links.all, options)
end
- expose :only_allow_merge_if_build_succeeds
+ expose :only_allow_merge_if_pipeline_succeeds
expose :request_access_enabled
expose :only_allow_merge_if_all_discussions_are_resolved
@@ -288,7 +288,7 @@ module API
expose :label_names, as: :labels
expose :work_in_progress?, as: :work_in_progress
expose :milestone, using: Entities::Milestone
- expose :merge_when_build_succeeds
+ expose :merge_when_pipeline_succeeds
expose :merge_status
expose :diff_head_sha, as: :sha
expose :merge_commit_sha
@@ -394,7 +394,8 @@ module API
expose :target_type
expose :target do |todo, options|
- Entities.const_get(todo.target_type).represent(todo.target, options)
+ target = todo.target_type == 'Commit' ? 'RepoCommit' : todo.target_type
+ Entities.const_get(target).represent(todo.target, options)
end
expose :target_url do |todo, options|
@@ -557,6 +558,7 @@ module API
expose :default_project_visibility
expose :default_snippet_visibility
expose :default_group_visibility
+ expose :default_artifacts_expire_in
expose :domain_whitelist
expose :domain_blacklist_enabled
expose :domain_blacklist
@@ -617,6 +619,10 @@ module API
end
end
+ class RunnerRegistrationDetails < Grape::Entity
+ expose :id, :token
+ end
+
class BuildArtifactFile < Grape::Entity
expose :filename, :size
end
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
index 1a7e68f0528..ebe8c3a5b2c 100644
--- a/lib/api/environments.rb
+++ b/lib/api/environments.rb
@@ -79,7 +79,24 @@ module API
environment = user_project.environments.find(params[:environment_id])
- present environment.destroy, with: Entities::Environment
+ environment.destroy
+ end
+
+ desc 'Stops an existing environment' do
+ success Entities::Environment
+ end
+ params do
+ requires :environment_id, type: Integer, desc: 'The environment ID'
+ end
+ post ':id/environments/:environment_id/stop' do
+ authorize! :create_deployment, user_project
+
+ environment = user_project.environments.find(params[:environment_id])
+
+ environment.stop_with_action!(current_user)
+
+ status 200
+ present environment, with: Entities::Environment
end
end
end
diff --git a/lib/api/files.rb b/lib/api/files.rb
index 500f9d3c787..9c4e43d77cc 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -118,10 +118,7 @@ module API
file_params = declared_params(include_missing: false)
result = ::Files::DestroyService.new(user_project, current_user, commit_params(file_params)).execute
- if result[:status] == :success
- status(200)
- commit_response(file_params)
- else
+ if result[:status] != :success
render_api_error!(result[:message], 400)
end
end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 9f29c4466ab..9cffd6180ae 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -36,12 +36,15 @@ module API
optional :skip_groups, type: Array[Integer], desc: 'Array of group ids to exclude from list'
optional :all_available, type: Boolean, desc: 'Show all group that you have access to'
optional :search, type: String, desc: 'Search for a specific group'
+ optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user'
optional :order_by, type: String, values: %w[name path], default: 'name', desc: 'Order by name or path'
optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)'
use :pagination
end
get do
- groups = if current_user.admin
+ groups = if params[:owned]
+ current_user.owned_groups
+ elsif current_user.admin
Group.all
elsif params[:all_available]
GroupsFinder.new.execute(current_user)
@@ -56,17 +59,6 @@ module API
present_groups groups, statistics: params[:statistics] && current_user.is_admin?
end
- desc 'Get list of owned groups for authenticated user' do
- success Entities::Group
- end
- params do
- use :pagination
- use :statistics_params
- end
- get '/owned' do
- present_groups current_user.owned_groups, statistics: params[:statistics]
- end
-
desc 'Create a group. Available only for users who can create groups.' do
success Entities::Group
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index d0efa7b993b..72d2b320077 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -209,6 +209,10 @@ module API
render_api_error!('204 No Content', 204)
end
+ def accepted!
+ render_api_error!('202 Accepted', 202)
+ end
+
def render_validation_error!(model)
if model.errors.any?
render_api_error!(model.errors.messages || '400 Bad Request', 400)
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
new file mode 100644
index 00000000000..119ca81b883
--- /dev/null
+++ b/lib/api/helpers/runner.rb
@@ -0,0 +1,23 @@
+module API
+ module Helpers
+ module Runner
+ def runner_registration_token_valid?
+ ActiveSupport::SecurityUtils.variable_size_secure_compare(params[:token],
+ current_application_settings.runners_registration_token)
+ end
+
+ def get_runner_version_from_params
+ return unless params['info'].present?
+ attributes_for_keys(%w(name version revision platform architecture), params['info'])
+ end
+
+ def authenticate_runner!
+ forbidden! unless current_runner
+ end
+
+ def current_runner
+ @runner ||= ::Ci::Runner.find_by_token(params[:token].to_s)
+ end
+ end
+ end
+end
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index d235977fbd8..7eed93aba00 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -132,6 +132,18 @@ module API
{ success: true, recovery_codes: codes }
end
+
+ post "/notify_post_receive" do
+ status 200
+
+ return unless Gitlab::GitalyClient.enabled?
+
+ begin
+ Gitlab::GitalyClient::Notifications.new.post_receive(params[:repo_path])
+ rescue GRPC::Unavailable => e
+ render_api_error(e, 500)
+ end
+ end
end
end
end
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index d2955af3f95..59f0e7cb647 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -1,7 +1,7 @@
module API
class Labels < Grape::API
include PaginationParams
-
+
before { authenticate! }
params do
@@ -56,7 +56,7 @@ module API
label = user_project.labels.find_by(title: params[:name])
not_found!('Label') unless label
- present label.destroy, with: Entities::Label, current_user: current_user, project: user_project
+ label.destroy
end
desc 'Update an existing label. At least one optional parameter is required.' do
diff --git a/lib/api/members.rb b/lib/api/members.rb
index 8360c007005..baf85e6075a 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -55,7 +55,6 @@ module API
authorize_admin_source!(source_type, source)
member = source.members.find_by(user_id: params[:user_id])
-
conflict!('Member already exists') if member
member = source.add_user(params[:user_id], params[:access_level], current_user: current_user, expires_at: params[:expires_at])
@@ -63,9 +62,6 @@ module API
if member.persisted? && member.valid?
present member.user, with: Entities::Member, member: member
else
- # This is to ensure back-compatibility but 400 behavior should be used
- # for all validation errors in 9.0!
- render_api_error!('Access level is not known', 422) if member.errors.key?(:access_level)
render_validation_error!(member)
end
end
@@ -87,9 +83,6 @@ module API
if member.update_attributes(declared_params(include_missing: false))
present member.user, with: Entities::Member, member: member
else
- # This is to ensure back-compatibility but 400 behavior should be used
- # for all validation errors in 9.0!
- render_api_error!('Access level is not known', 422) if member.errors.key?(:access_level)
render_validation_error!(member)
end
end
@@ -100,24 +93,10 @@ module API
end
delete ":id/members/:user_id" do
source = find_source(source_type, params[:id])
+ # Ensure that memeber exists
+ source.members.find_by!(user_id: params[:user_id])
- # This is to ensure back-compatibility but find_by! should be used
- # in that casse in 9.0!
- member = source.members.find_by(user_id: params[:user_id])
-
- # This is to ensure back-compatibility but this should be removed in
- # favor of find_by! in 9.0!
- not_found!("Member: user_id:#{params[:user_id]}") if source_type == 'group' && member.nil?
-
- # This is to ensure back-compatibility but 204 behavior should be used
- # for all DELETE endpoints in 9.0!
- if member.nil?
- { message: "Access revoked", id: params[:user_id].to_i }
- else
- ::Members::DestroyService.new(source, current_user, declared_params).execute
-
- present member.user, with: Entities::Member, member: member
- end
+ ::Members::DestroyService.new(source, current_user, declared_params).execute
end
end
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index bdd764abfeb..4638a66811d 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -168,8 +168,8 @@ module API
optional :merge_commit_message, type: String, desc: 'Custom merge commit message'
optional :should_remove_source_branch, type: Boolean,
desc: 'When true, the source branch will be deleted if possible'
- optional :merge_when_build_succeeds, type: Boolean,
- desc: 'When true, this merge request will be merged when the pipeline succeeds'
+ optional :merge_when_pipeline_succeeds, type: Boolean,
+ desc: 'When true, this merge request will be merged when the pipeline succeeds'
optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch'
end
put ':id/merge_requests/:merge_request_id/merge' do
@@ -192,7 +192,7 @@ module API
should_remove_source_branch: params[:should_remove_source_branch]
}
- if params[:merge_when_build_succeeds] && merge_request.head_pipeline && merge_request.head_pipeline.active?
+ if params[:merge_when_pipeline_succeeds] && merge_request.head_pipeline && merge_request.head_pipeline.active?
::MergeRequests::MergeWhenPipelineSucceedsService
.new(merge_request.target_project, current_user, merge_params)
.execute(merge_request)
@@ -208,10 +208,10 @@ module API
desc 'Cancel merge if "Merge When Pipeline Succeeds" is enabled' do
success Entities::MergeRequest
end
- post ':id/merge_requests/:merge_request_id/cancel_merge_when_build_succeeds' do
+ post ':id/merge_requests/:merge_request_id/cancel_merge_when_pipeline_succeeds' do
merge_request = find_project_merge_request(params[:merge_request_id])
- unauthorized! unless merge_request.can_cancel_merge_when_build_succeeds?(current_user)
+ unauthorized! unless merge_request.can_cancel_merge_when_pipeline_succeeds?(current_user)
::MergeRequest::MergeWhenPipelineSucceedsService
.new(merge_request.target_project, current_user)
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index f559a7f74a0..3b3e45cbd06 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -132,8 +132,6 @@ module API
authorize! :admin_note, note
::Notes::DestroyService.new(user_project, current_user).execute(note)
-
- present note, with: Entities::Note
end
end
end
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index f7a28d7ad10..57a5f97dc7f 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -90,12 +90,9 @@ module API
requires :hook_id, type: Integer, desc: 'The ID of the hook to delete'
end
delete ":id/hooks/:hook_id" do
- begin
- present user_project.hooks.destroy(params[:hook_id]), with: Entities::ProjectHook
- rescue
- # ProjectHook can raise Error if hook_id not found
- not_found!("Error deleting hook #{params[:hook_id]}")
- end
+ hook = user_project.hooks.find(params.delete(:hook_id))
+
+ hook.destroy
end
end
end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index e7b891bd92e..996404e0e49 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -23,7 +23,7 @@ module API
], desc: 'Create a public project. The same as visibility_level = 20.'
optional :public_builds, type: Boolean, desc: 'Perform public builds'
optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
- optional :only_allow_merge_if_build_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
+ optional :only_allow_merge_if_pipeline_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved'
end
end
@@ -94,8 +94,9 @@ module API
success Entities::Project
end
params do
- requires :name, type: String, desc: 'The name of the project'
+ optional :name, type: String, desc: 'The name of the project'
optional :path, type: String, desc: 'The path of the repository'
+ at_least_one_of :name, :path
use :optional_params
use :create_params
end
@@ -208,7 +209,7 @@ module API
:wiki_enabled, :builds_enabled, :snippets_enabled,
:shared_runners_enabled, :container_registry_enabled,
:lfs_enabled, :visibility_level, :public_builds,
- :request_access_enabled, :only_allow_merge_if_build_succeeds,
+ :request_access_enabled, :only_allow_merge_if_pipeline_succeeds,
:only_allow_merge_if_all_discussions_are_resolved, :path,
:default_branch
end
@@ -282,6 +283,8 @@ module API
delete ":id" do
authorize! :remove_project, user_project
::Projects::DestroyService.new(user_project, current_user, {}).async_execute
+
+ accepted!
end
desc 'Mark this project as forked from another'
@@ -351,7 +354,6 @@ module API
not_found!('Group Link') unless link
link.destroy
- no_content!
end
desc 'Upload a file'
diff --git a/lib/api/runner.rb b/lib/api/runner.rb
new file mode 100644
index 00000000000..47858f1866b
--- /dev/null
+++ b/lib/api/runner.rb
@@ -0,0 +1,52 @@
+module API
+ class Runner < Grape::API
+ helpers ::API::Helpers::Runner
+
+ resource :runners do
+ desc 'Registers a new Runner' do
+ success Entities::RunnerRegistrationDetails
+ http_codes [[201, 'Runner was created'], [403, 'Forbidden']]
+ end
+ params do
+ requires :token, type: String, desc: 'Registration token'
+ optional :description, type: String, desc: %q(Runner's description)
+ optional :info, type: Hash, desc: %q(Runner's metadata)
+ optional :locked, type: Boolean, desc: 'Should Runner be locked for current project'
+ optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs'
+ optional :tag_list, type: Array[String], desc: %q(List of Runner's tags)
+ end
+ post '/' do
+ attributes = attributes_for_keys [:description, :locked, :run_untagged, :tag_list]
+
+ runner =
+ if runner_registration_token_valid?
+ # Create shared runner. Requires admin access
+ Ci::Runner.create(attributes.merge(is_shared: true))
+ elsif project = Project.find_by(runners_token: params[:token])
+ # Create a specific runner for project.
+ project.runners.create(attributes)
+ end
+
+ return forbidden! unless runner
+
+ if runner.id
+ runner.update(get_runner_version_from_params)
+ present runner, with: Entities::RunnerRegistrationDetails
+ else
+ not_found!
+ end
+ end
+
+ desc 'Deletes a registered Runner' do
+ http_codes [[204, 'Runner was deleted'], [403, 'Forbidden']]
+ end
+ params do
+ requires :token, type: String, desc: %q(Runner's authentication token)
+ end
+ delete '/' do
+ authenticate_runner!
+ Ci::Runner.find_by_token(params[:token]).destroy
+ end
+ end
+ end
+end
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index 252e59bfa58..2e41f16f8c6 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -78,9 +78,8 @@ module API
delete ':id' do
runner = get_runner(params[:id])
authenticate_delete_runner!(runner)
- runner.destroy!
- present runner, with: Entities::Runner
+ runner.destroy!
end
end
@@ -136,8 +135,6 @@ module API
forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1
runner_project.destroy
-
- present runner, with: Entities::Runner
end
end
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 1456fe4688b..79a5f27dc4d 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -563,7 +563,20 @@ module API
SlackService,
MattermostService,
TeamcityService,
- ].freeze
+ ]
+
+ if Rails.env.development?
+ services['mock-ci'] = [
+ {
+ required: true,
+ name: :mock_service_url,
+ type: String,
+ desc: 'URL to the mock service'
+ }
+ ]
+
+ service_classes << MockCiService
+ end
trigger_services = {
'mattermost-slash-commands' => [
@@ -641,9 +654,7 @@ module API
hash.merge!(key => nil)
end
- if service.update_attributes(attrs.merge(active: false))
- true
- else
+ unless service.update_attributes(attrs.merge(active: false))
render_api_error!('400 Bad Request', 400)
end
end
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 747ceb4e3e0..936c7e0930b 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -56,7 +56,8 @@ module API
given shared_runners_enabled: ->(val) { val } do
requires :shared_runners_text, type: String, desc: 'Shared runners text '
end
- optional :max_artifacts_size, type: Integer, desc: "Set the maximum file size each build's artifacts can have"
+ optional :max_artifacts_size, type: Integer, desc: "Set the maximum file size for each job's artifacts"
+ optional :default_artifacts_expire_in, type: String, desc: "Set the default expiration time for each job's artifacts"
optional :max_pages_size, type: Integer, desc: 'Maximum size of pages in MB'
optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)'
optional :metrics_enabled, type: Boolean, desc: 'Enable the InfluxDB metrics'
@@ -117,7 +118,9 @@ module API
:send_user_confirmation_email, :domain_whitelist, :domain_blacklist_enabled,
:after_sign_up_text, :signin_enabled, :require_two_factor_authentication,
:home_page_url, :after_sign_out_path, :sign_in_text, :help_page_text,
- :shared_runners_enabled, :max_artifacts_size, :max_pages_size, :container_registry_token_expire_delay,
+ :shared_runners_enabled, :max_artifacts_size,
+ :default_artifacts_expire_in, :max_pages_size,
+ :container_registry_token_expire_delay,
:metrics_enabled, :sidekiq_throttling_enabled, :recaptcha_enabled,
:akismet_enabled, :admin_notification_email, :sentry_enabled,
:repository_storage, :repository_checks_enabled, :koding_enabled, :plantuml_enabled,
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index ac03fbd2a3d..0f86fdb3075 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -118,9 +118,10 @@ module API
delete ':id' do
snippet = snippets_for_current_user.find_by(id: params.delete(:id))
return not_found!('Snippet') unless snippet
+
authorize! :destroy_personal_snippet, snippet
+
snippet.destroy
- no_content!
end
desc 'Get a raw snippet' do
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
index d038a3fa828..ed7b23b474a 100644
--- a/lib/api/system_hooks.rb
+++ b/lib/api/system_hooks.rb
@@ -66,7 +66,7 @@ module API
hook = SystemHook.find_by(id: params[:id])
not_found!('System hook') unless hook
- present hook.destroy, with: Entities::Hook
+ hook.destroy
end
end
end
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index 86759ab882f..d31ef9de26b 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -66,11 +66,7 @@ module API
result = ::Tags::DestroyService.new(user_project, current_user).
execute(params[:tag_name])
- if result[:status] == :success
- {
- tag_name: params[:tag_name]
- }
- else
+ if result[:status] != :success
render_api_error!(result[:message], result[:return_code])
end
end
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index 87a717ba751..b7c9c5f2b7f 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -21,14 +21,9 @@ module API
unauthorized! unless trigger.project == project
# validate variables
- variables = params[:variables]
- if variables
- unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) }
- render_api_error!('variables needs to be a map of key-valued strings', 400)
- end
-
- # convert variables from Mash to Hash
- variables = variables.to_h
+ variables = params[:variables].to_h
+ unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) }
+ render_api_error!('variables needs to be a map of key-valued strings', 400)
end
# create request and trigger builds
@@ -98,8 +93,6 @@ module API
return not_found!('Trigger') unless trigger
trigger.destroy
-
- present trigger, with: Entities::Trigger
end
end
end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 94b2b6653d2..7bb4b76f830 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -236,7 +236,7 @@ module API
key = user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key
- present key.destroy, with: Entities::SSHKey
+ key.destroy
end
desc 'Add an email address to a specified user. Available only for admins.' do
@@ -422,7 +422,7 @@ module API
key = current_user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key
- present key.destroy, with: Entities::SSHKey
+ key.destroy
end
desc "Get the currently authenticated user's email addresses" do
diff --git a/lib/api/v3/award_emoji.rb b/lib/api/v3/award_emoji.rb
new file mode 100644
index 00000000000..1e35283631f
--- /dev/null
+++ b/lib/api/v3/award_emoji.rb
@@ -0,0 +1,59 @@
+module API
+ module V3
+ class AwardEmoji < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+ AWARDABLES = %w[issue merge_request snippet].freeze
+
+ resource :projects do
+ AWARDABLES.each do |awardable_type|
+ awardable_string = awardable_type.pluralize
+ awardable_id_string = "#{awardable_type}_id"
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ requires :"#{awardable_id_string}", type: Integer, desc: "The ID of an Issue, Merge Request or Snippet"
+ end
+
+ [":id/#{awardable_string}/:#{awardable_id_string}/award_emoji",
+ ":id/#{awardable_string}/:#{awardable_id_string}/notes/:note_id/award_emoji"].each do |endpoint|
+ desc 'Delete a +awardables+ award emoji' do
+ detail 'This feature was introduced in 8.9'
+ success ::API::Entities::AwardEmoji
+ end
+ params do
+ requires :award_id, type: Integer, desc: 'The ID of an award emoji'
+ end
+ delete "#{endpoint}/:award_id" do
+ award = awardable.award_emoji.find(params[:award_id])
+
+ unauthorized! unless award.user == current_user || current_user.admin?
+
+ present award.destroy, with: ::API::Entities::AwardEmoji
+ end
+ end
+ end
+ end
+
+ helpers do
+ def awardable
+ @awardable ||=
+ begin
+ if params.include?(:note_id)
+ note_id = params.delete(:note_id)
+
+ awardable.notes.find(note_id)
+ elsif params.include?(:issue_id)
+ user_project.issues.find(params[:issue_id])
+ elsif params.include?(:merge_request_id)
+ user_project.merge_requests.find(params[:merge_request_id])
+ else
+ user_project.snippets.find(params[:snippet_id])
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/boards.rb b/lib/api/v3/boards.rb
index 31d708bc2c8..b1c2a3c59f2 100644
--- a/lib/api/v3/boards.rb
+++ b/lib/api/v3/boards.rb
@@ -44,6 +44,27 @@ module API
authorize!(:read_board, user_project)
present board_lists, with: ::API::Entities::List
end
+
+ desc 'Delete a board list' do
+ detail 'This feature was introduced in 8.13'
+ success ::API::Entities::List
+ end
+ params do
+ requires :list_id, type: Integer, desc: 'The ID of a board list'
+ end
+ delete "/lists/:list_id" do
+ authorize!(:admin_list, user_project)
+
+ list = board_lists.find(params[:list_id])
+
+ service = ::Boards::Lists::DestroyService.new(user_project, current_user)
+
+ if service.execute(list)
+ present list, with: ::API::Entities::List
+ else
+ render_api_error!({ error: 'List could not be deleted!' }, 400)
+ end
+ end
end
end
end
diff --git a/lib/api/v3/branches.rb b/lib/api/v3/branches.rb
index 733c6b21be5..699e41b5537 100644
--- a/lib/api/v3/branches.rb
+++ b/lib/api/v3/branches.rb
@@ -18,6 +18,33 @@ module API
present branches, with: ::API::Entities::RepoBranch, project: user_project
end
+
+ desc 'Delete a branch'
+ params do
+ requires :branch, type: String, desc: 'The name of the branch'
+ end
+ delete ":id/repository/branches/:branch", requirements: { branch: /.+/ } do
+ authorize_push_project
+
+ result = DeleteBranchService.new(user_project, current_user).
+ execute(params[:branch])
+
+ if result[:status] == :success
+ status(200)
+ {
+ branch_name: params[:branch]
+ }
+ else
+ render_api_error!(result[:message], result[:return_code])
+ end
+ end
+
+ desc 'Delete all merged branches'
+ delete ":id/repository/merged_branches" do
+ DeleteMergedBranchesService.new(user_project, current_user).async_execute
+
+ status(200)
+ end
end
end
end
diff --git a/lib/api/v3/broadcast_messages.rb b/lib/api/v3/broadcast_messages.rb
new file mode 100644
index 00000000000..417e4ad0b26
--- /dev/null
+++ b/lib/api/v3/broadcast_messages.rb
@@ -0,0 +1,31 @@
+module API
+ module V3
+ class BroadcastMessages < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+ before { authenticated_as_admin! }
+
+ resource :broadcast_messages do
+ helpers do
+ def find_message
+ BroadcastMessage.find(params[:id])
+ end
+ end
+
+ desc 'Delete a broadcast message' do
+ detail 'This feature was introduced in GitLab 8.12.'
+ success ::API::Entities::BroadcastMessage
+ end
+ params do
+ requires :id, type: Integer, desc: 'Broadcast message ID'
+ end
+ delete ':id' do
+ message = find_message
+
+ present message.destroy, with: ::API::Entities::BroadcastMessage
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/commits.rb b/lib/api/v3/commits.rb
index 126cc95fc3d..506204b3517 100644
--- a/lib/api/v3/commits.rb
+++ b/lib/api/v3/commits.rb
@@ -55,13 +55,6 @@ module API
branch = attrs.delete(:branch_name)
attrs.merge!(branch: branch, start_branch: branch, target_branch: branch)
- attrs[:actions].map! do |action|
- action[:action] = action[:action].to_sym
- action[:file_path].slice!(0) if action[:file_path] && action[:file_path].start_with?('/')
- action[:previous_path].slice!(0) if action[:previous_path] && action[:previous_path].start_with?('/')
- action
- end
-
result = ::Files::MultiService.new(user_project, current_user, attrs).execute
if result[:status] == :success
diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb
index 11d0e6dbf71..2a3dcb7f288 100644
--- a/lib/api/v3/entities.rb
+++ b/lib/api/v3/entities.rb
@@ -45,6 +45,86 @@ module API
expose :created_at, :updated_at
expose :awardable_id, :awardable_type
end
+
+ class Project < Grape::Entity
+ expose :id, :description, :default_branch, :tag_list
+ expose :public?, as: :public
+ expose :archived?, as: :archived
+ expose :visibility_level, :ssh_url_to_repo, :http_url_to_repo, :web_url
+ expose :owner, using: ::API::Entities::UserBasic, unless: ->(project, options) { project.group }
+ expose :name, :name_with_namespace
+ expose :path, :path_with_namespace
+ expose :container_registry_enabled
+
+ # Expose old field names with the new permissions methods to keep API compatible
+ expose(:issues_enabled) { |project, options| project.feature_available?(:issues, options[:current_user]) }
+ expose(:merge_requests_enabled) { |project, options| project.feature_available?(:merge_requests, options[:current_user]) }
+ expose(:wiki_enabled) { |project, options| project.feature_available?(:wiki, options[:current_user]) }
+ expose(:builds_enabled) { |project, options| project.feature_available?(:builds, options[:current_user]) }
+ expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:current_user]) }
+
+ expose :created_at, :last_activity_at
+ expose :shared_runners_enabled
+ expose :lfs_enabled?, as: :lfs_enabled
+ expose :creator_id
+ expose :namespace, using: 'API::Entities::Namespace'
+ expose :forked_from_project, using: ::API::Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? }
+ expose :avatar_url
+ expose :star_count, :forks_count
+ expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) && project.default_issues_tracker? }
+ expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
+ expose :public_builds
+ expose :shared_with_groups do |project, options|
+ ::API::Entities::SharedGroup.represent(project.project_group_links.all, options)
+ end
+ expose :only_allow_merge_if_pipeline_succeeds, as: :only_allow_merge_if_build_succeeds
+ expose :request_access_enabled
+ expose :only_allow_merge_if_all_discussions_are_resolved
+
+ expose :statistics, using: 'API::Entities::ProjectStatistics', if: :statistics
+ end
+
+ class ProjectWithAccess < Project
+ expose :permissions do
+ expose :project_access, using: ::API::Entities::ProjectAccess do |project, options|
+ project.project_members.find_by(user_id: options[:current_user].id)
+ end
+
+ expose :group_access, using: ::API::Entities::GroupAccess do |project, options|
+ if project.group
+ project.group.group_members.find_by(user_id: options[:current_user].id)
+ end
+ end
+ end
+ end
+
+ class MergeRequest < Grape::Entity
+ expose :id, :iid
+ expose(:project_id) { |entity| entity.project.id }
+ expose :title, :description
+ expose :state, :created_at, :updated_at
+ expose :target_branch, :source_branch
+ expose :upvotes, :downvotes
+ expose :author, :assignee, using: ::API::Entities::UserBasic
+ expose :source_project_id, :target_project_id
+ expose :label_names, as: :labels
+ expose :work_in_progress?, as: :work_in_progress
+ expose :milestone, using: ::API::Entities::Milestone
+ expose :merge_when_pipeline_succeeds, as: :merge_when_build_succeeds
+ expose :merge_status
+ expose :diff_head_sha, as: :sha
+ expose :merge_commit_sha
+ expose :subscribed do |merge_request, options|
+ merge_request.subscribed?(options[:current_user], options[:project])
+ end
+ expose :user_notes_count
+ expose :should_remove_source_branch?, as: :should_remove_source_branch
+ expose :force_remove_source_branch?, as: :force_remove_source_branch
+
+ expose :web_url do |merge_request, options|
+ Gitlab::UrlBuilder.build(merge_request)
+ end
+ end
end
end
end
diff --git a/lib/api/v3/environments.rb b/lib/api/v3/environments.rb
new file mode 100644
index 00000000000..3effccfa708
--- /dev/null
+++ b/lib/api/v3/environments.rb
@@ -0,0 +1,29 @@
+module API
+ module V3
+ class Environments < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+
+ params do
+ requires :id, type: String, desc: 'The project ID'
+ end
+ resource :projects do
+ desc 'Deletes an existing environment' do
+ detail 'This feature was introduced in GitLab 8.11.'
+ success ::API::Entities::Environment
+ end
+ params do
+ requires :environment_id, type: Integer, desc: 'The environment ID'
+ end
+ delete ':id/environments/:environment_id' do
+ authorize! :update_environment, user_project
+
+ environment = user_project.environments.find(params[:environment_id])
+
+ present environment.destroy, with: ::API::Entities::Environment
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/groups.rb b/lib/api/v3/groups.rb
new file mode 100644
index 00000000000..c826bc4fe0b
--- /dev/null
+++ b/lib/api/v3/groups.rb
@@ -0,0 +1,38 @@
+module API
+ module V3
+ class Groups < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+
+ helpers do
+ params :statistics_params do
+ optional :statistics, type: Boolean, default: false, desc: 'Include project statistics'
+ end
+
+ def present_groups(groups, options = {})
+ options = options.reverse_merge(
+ with: ::API::Entities::Group,
+ current_user: current_user,
+ )
+
+ groups = groups.with_statistics if options[:statistics]
+ present paginate(groups), options
+ end
+ end
+
+ resource :groups do
+ desc 'Get list of owned groups for authenticated user' do
+ success ::API::Entities::Group
+ end
+ params do
+ use :pagination
+ use :statistics_params
+ end
+ get '/owned' do
+ present_groups current_user.owned_groups, statistics: params[:statistics]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/issues.rb b/lib/api/v3/issues.rb
index d0af09f0e1e..5d7dfabfcd6 100644
--- a/lib/api/v3/issues.rb
+++ b/lib/api/v3/issues.rb
@@ -226,6 +226,8 @@ module API
not_found!('Issue') unless issue
authorize!(:destroy_issue, issue)
+
+ status(200)
issue.destroy
end
end
diff --git a/lib/api/v3/labels.rb b/lib/api/v3/labels.rb
index 5c3261311bf..41f45d244e3 100644
--- a/lib/api/v3/labels.rb
+++ b/lib/api/v3/labels.rb
@@ -13,6 +13,21 @@ module API
get ':id/labels' do
present available_labels, with: ::API::Entities::Label, current_user: current_user, project: user_project
end
+
+ desc 'Delete an existing label' do
+ success ::API::Entities::Label
+ end
+ params do
+ requires :name, type: String, desc: 'The name of the label to be deleted'
+ end
+ delete ':id/labels' do
+ authorize! :admin_label, user_project
+
+ label = user_project.labels.find_by(title: params[:name])
+ not_found!('Label') unless label
+
+ present label.destroy, with: ::API::Entities::Label, current_user: current_user, project: user_project
+ end
end
end
end
diff --git a/lib/api/v3/members.rb b/lib/api/v3/members.rb
index 19f276d5484..3d4972afd9d 100644
--- a/lib/api/v3/members.rb
+++ b/lib/api/v3/members.rb
@@ -119,6 +119,7 @@ module API
# This is to ensure back-compatibility but 204 behavior should be used
# for all DELETE endpoints in 9.0!
if member.nil?
+ status(200 )
{ message: "Access revoked", id: params[:user_id].to_i }
else
::Members::DestroyService.new(source, current_user, declared_params).execute
diff --git a/lib/api/v3/merge_requests.rb b/lib/api/v3/merge_requests.rb
index 129f9d850e9..654e818e1b5 100644
--- a/lib/api/v3/merge_requests.rb
+++ b/lib/api/v3/merge_requests.rb
@@ -39,7 +39,7 @@ module API
desc 'List merge requests' do
detail 'iid filter is deprecated have been removed on V4'
- success ::API::Entities::MergeRequest
+ success ::API::V3::Entities::MergeRequest
end
params do
optional :state, type: String, values: %w[opened closed merged all], default: 'all',
@@ -66,11 +66,11 @@ module API
end
merge_requests = merge_requests.reorder(params[:order_by] => params[:sort])
- present paginate(merge_requests), with: ::API::Entities::MergeRequest, current_user: current_user, project: user_project
+ present paginate(merge_requests), with: ::API::V3::Entities::MergeRequest, current_user: current_user, project: user_project
end
desc 'Create a merge request' do
- success ::API::Entities::MergeRequest
+ success ::API::V3::Entities::MergeRequest
end
params do
requires :title, type: String, desc: 'The title of the merge request'
@@ -89,7 +89,7 @@ module API
merge_request = ::MergeRequests::CreateService.new(user_project, current_user, mr_params).execute
if merge_request.valid?
- present merge_request, with: ::API::Entities::MergeRequest, current_user: current_user, project: user_project
+ present merge_request, with: ::API::V3::Entities::MergeRequest, current_user: current_user, project: user_project
else
handle_merge_request_errors! merge_request.errors
end
@@ -103,6 +103,8 @@ module API
merge_request = find_project_merge_request(params[:merge_request_id])
authorize!(:destroy_merge_request, merge_request)
+
+ status(200)
merge_request.destroy
end
@@ -114,12 +116,12 @@ module API
if status == :deprecated
detail DEPRECATION_MESSAGE
end
- success ::API::Entities::MergeRequest
+ success ::API::V3::Entities::MergeRequest
end
get path do
merge_request = find_merge_request_with_access(params[:merge_request_id])
- present merge_request, with: ::API::Entities::MergeRequest, current_user: current_user, project: user_project
+ present merge_request, with: ::API::V3::Entities::MergeRequest, current_user: current_user, project: user_project
end
desc 'Get the commits of a merge request' do
@@ -141,7 +143,7 @@ module API
end
desc 'Update a merge request' do
- success ::API::Entities::MergeRequest
+ success ::API::V3::Entities::MergeRequest
end
params do
optional :title, type: String, allow_blank: false, desc: 'The title of the merge request'
@@ -162,21 +164,21 @@ module API
merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, mr_params).execute(merge_request)
if merge_request.valid?
- present merge_request, with: ::API::Entities::MergeRequest, current_user: current_user, project: user_project
+ present merge_request, with: ::API::V3::Entities::MergeRequest, current_user: current_user, project: user_project
else
handle_merge_request_errors! merge_request.errors
end
end
desc 'Merge a merge request' do
- success ::API::Entities::MergeRequest
+ success ::API::V3::Entities::MergeRequest
end
params do
optional :merge_commit_message, type: String, desc: 'Custom merge commit message'
optional :should_remove_source_branch, type: Boolean,
desc: 'When true, the source branch will be deleted if possible'
optional :merge_when_build_succeeds, type: Boolean,
- desc: 'When true, this merge request will be merged when the pipeline succeeds'
+ desc: 'When true, this merge request will be merged when the build succeeds'
optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch'
end
put "#{path}/merge" do
@@ -209,16 +211,16 @@ module API
.execute(merge_request)
end
- present merge_request, with: ::API::Entities::MergeRequest, current_user: current_user, project: user_project
+ present merge_request, with: ::API::V3::Entities::MergeRequest, current_user: current_user, project: user_project
end
- desc 'Cancel merge if "Merge When Pipeline Succeeds" is enabled' do
- success ::API::Entities::MergeRequest
+ desc 'Cancel merge if "Merge When Build succeeds" is enabled' do
+ success ::API::V3::Entities::MergeRequest
end
post "#{path}/cancel_merge_when_build_succeeds" do
merge_request = find_project_merge_request(params[:merge_request_id])
- unauthorized! unless merge_request.can_cancel_merge_when_build_succeeds?(current_user)
+ unauthorized! unless merge_request.can_cancel_merge_when_pipeline_succeeds?(current_user)
::MergeRequest::MergeWhenPipelineSucceedsService
.new(merge_request.target_project, current_user)
diff --git a/lib/api/v3/project_snippets.rb b/lib/api/v3/project_snippets.rb
index e03e941d30b..809ca4f37ba 100644
--- a/lib/api/v3/project_snippets.rb
+++ b/lib/api/v3/project_snippets.rb
@@ -121,6 +121,8 @@ module API
authorize! :admin_project_snippet, snippet
snippet.destroy
+
+ status(200)
end
desc 'Get a raw project snippet'
diff --git a/lib/api/v3/projects.rb b/lib/api/v3/projects.rb
index c3821555452..47bfc12035a 100644
--- a/lib/api/v3/projects.rb
+++ b/lib/api/v3/projects.rb
@@ -5,6 +5,10 @@ module API
before { authenticate_non_get! }
+ after_validation do
+ set_only_allow_merge_if_pipeline_succeeds!
+ end
+
helpers do
params :optional_params do
optional :description, type: String, desc: 'The description of the project'
@@ -25,6 +29,7 @@ module API
optional :public_builds, type: Boolean, desc: 'Perform public builds'
optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
optional :only_allow_merge_if_build_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
+ optional :only_allow_merge_if_pipeline_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved'
end
@@ -37,6 +42,12 @@ module API
end
attrs
end
+
+ def set_only_allow_merge_if_pipeline_succeeds!
+ if params.has_key?(:only_allow_merge_if_build_succeeds)
+ params[:only_allow_merge_if_pipeline_succeeds] = params.delete(:only_allow_merge_if_build_succeeds)
+ end
+ end
end
resource :projects do
@@ -75,7 +86,7 @@ module API
def present_projects(projects, options = {})
options = options.reverse_merge(
- with: ::API::Entities::Project,
+ with: ::API::V3::Entities::Project,
current_user: current_user,
simple: params[:simple],
)
@@ -95,7 +106,7 @@ module API
use :collection_params
end
get '/visible' do
- entity = current_user ? ::API::Entities::ProjectWithAccess : ::API::Entities::BasicProjectDetails
+ entity = current_user ? ::API::V3::Entities::ProjectWithAccess : ::API::Entities::BasicProjectDetails
present_projects ProjectsFinder.new.execute(current_user), with: entity
end
@@ -109,7 +120,7 @@ module API
authenticate!
present_projects current_user.authorized_projects,
- with: ::API::Entities::ProjectWithAccess
+ with: ::API::V3::Entities::ProjectWithAccess
end
desc 'Get an owned projects list for authenticated user' do
@@ -123,7 +134,7 @@ module API
authenticate!
present_projects current_user.owned_projects,
- with: ::API::Entities::ProjectWithAccess,
+ with: ::API::V3::Entities::ProjectWithAccess,
statistics: params[:statistics]
end
@@ -149,11 +160,11 @@ module API
get '/all' do
authenticated_as_admin!
- present_projects Project.all, with: ::API::Entities::ProjectWithAccess, statistics: params[:statistics]
+ present_projects Project.all, with: ::API::V3::Entities::ProjectWithAccess, statistics: params[:statistics]
end
desc 'Search for projects the current user has access to' do
- success ::API::Entities::Project
+ success ::API::V3::Entities::Project
end
params do
requires :query, type: String, desc: 'The project name to be searched'
@@ -165,15 +176,16 @@ module API
projects = search_service.objects('projects', params[:page])
projects = projects.reorder(params[:order_by] => params[:sort])
- present paginate(projects), with: ::API::Entities::Project
+ present paginate(projects), with: ::API::V3::Entities::Project
end
desc 'Create new project' do
- success ::API::Entities::Project
+ success ::API::V3::Entities::Project
end
params do
- requires :name, type: String, desc: 'The name of the project'
+ optional :name, type: String, desc: 'The name of the project'
optional :path, type: String, desc: 'The path of the repository'
+ at_least_one_of :name, :path
use :optional_params
use :create_params
end
@@ -182,7 +194,7 @@ module API
project = ::Projects::CreateService.new(current_user, attrs).execute
if project.saved?
- present project, with: ::API::Entities::Project,
+ present project, with: ::API::V3::Entities::Project,
user_can_admin_project: can?(current_user, :admin_project, project)
else
if project.errors[:limit_reached].present?
@@ -193,7 +205,7 @@ module API
end
desc 'Create new project for a specified user. Only available to admin users.' do
- success ::API::Entities::Project
+ success ::API::V3::Entities::Project
end
params do
requires :name, type: String, desc: 'The name of the project'
@@ -211,7 +223,7 @@ module API
project = ::Projects::CreateService.new(user, attrs).execute
if project.saved?
- present project, with: ::API::Entities::Project,
+ present project, with: ::API::V3::Entities::Project,
user_can_admin_project: can?(current_user, :admin_project, project)
else
render_validation_error!(project)
@@ -224,10 +236,10 @@ module API
end
resource :projects, requirements: { id: /[^\/]+/ } do
desc 'Get a single project' do
- success ::API::Entities::ProjectWithAccess
+ success ::API::V3::Entities::ProjectWithAccess
end
get ":id" do
- entity = current_user ? ::API::Entities::ProjectWithAccess : ::API::Entities::BasicProjectDetails
+ entity = current_user ? ::API::V3::Entities::ProjectWithAccess : ::API::Entities::BasicProjectDetails
present user_project, with: entity, current_user: current_user,
user_can_admin_project: can?(current_user, :admin_project, user_project)
end
@@ -243,7 +255,7 @@ module API
end
desc 'Fork new project for the current user or provided namespace.' do
- success ::API::Entities::Project
+ success ::API::V3::Entities::Project
end
params do
optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into'
@@ -269,13 +281,13 @@ module API
if forked_project.errors.any?
conflict!(forked_project.errors.messages)
else
- present forked_project, with: ::API::Entities::Project,
+ present forked_project, with: ::API::V3::Entities::Project,
user_can_admin_project: can?(current_user, :admin_project, forked_project)
end
end
desc 'Update an existing project' do
- success ::API::Entities::Project
+ success ::API::V3::Entities::Project
end
params do
optional :name, type: String, desc: 'The name of the project'
@@ -299,7 +311,7 @@ module API
result = ::Projects::UpdateService.new(user_project, current_user, attrs).execute
if result[:status] == :success
- present user_project, with: ::API::Entities::Project,
+ present user_project, with: ::API::V3::Entities::Project,
user_can_admin_project: can?(current_user, :admin_project, user_project)
else
render_validation_error!(user_project)
@@ -307,29 +319,29 @@ module API
end
desc 'Archive a project' do
- success ::API::Entities::Project
+ success ::API::V3::Entities::Project
end
post ':id/archive' do
authorize!(:archive_project, user_project)
user_project.archive!
- present user_project, with: ::API::Entities::Project
+ present user_project, with: ::API::V3::Entities::Project
end
desc 'Unarchive a project' do
- success ::API::Entities::Project
+ success ::API::V3::Entities::Project
end
post ':id/unarchive' do
authorize!(:archive_project, user_project)
user_project.unarchive!
- present user_project, with: ::API::Entities::Project
+ present user_project, with: ::API::V3::Entities::Project
end
desc 'Star a project' do
- success ::API::Entities::Project
+ success ::API::V3::Entities::Project
end
post ':id/star' do
if current_user.starred?(user_project)
@@ -338,19 +350,19 @@ module API
current_user.toggle_star(user_project)
user_project.reload
- present user_project, with: ::API::Entities::Project
+ present user_project, with: ::API::V3::Entities::Project
end
end
desc 'Unstar a project' do
- success ::API::Entities::Project
+ success ::API::V3::Entities::Project
end
delete ':id/star' do
if current_user.starred?(user_project)
current_user.toggle_star(user_project)
user_project.reload
- present user_project, with: ::API::Entities::Project
+ present user_project, with: ::API::V3::Entities::Project
else
not_modified!
end
@@ -359,6 +371,8 @@ module API
desc 'Remove a project'
delete ":id" do
authorize! :remove_project, user_project
+
+ status(200)
::Projects::DestroyService.new(user_project, current_user, {}).async_execute
end
@@ -384,6 +398,7 @@ module API
authorize! :remove_fork_project, user_project
if user_project.forked?
+ status(200)
user_project.forked_project_link.destroy
else
not_modified!
diff --git a/lib/api/v3/runners.rb b/lib/api/v3/runners.rb
new file mode 100644
index 00000000000..8967141fe3d
--- /dev/null
+++ b/lib/api/v3/runners.rb
@@ -0,0 +1,65 @@
+module API
+ module V3
+ class Runners < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+
+ resource :runners do
+ desc 'Remove a runner' do
+ success ::API::Entities::Runner
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the runner'
+ end
+ delete ':id' do
+ runner = Ci::Runner.find(params[:id])
+ not_found!('Runner') unless runner
+
+ authenticate_delete_runner!(runner)
+
+ status(200)
+ runner.destroy
+ end
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ before { authorize_admin_project }
+
+ desc "Disable project's runner" do
+ success ::API::Entities::Runner
+ end
+ params do
+ requires :runner_id, type: Integer, desc: 'The ID of the runner'
+ end
+ delete ':id/runners/:runner_id' do
+ runner_project = user_project.runner_projects.find_by(runner_id: params[:runner_id])
+ not_found!('Runner') unless runner_project
+
+ runner = runner_project.runner
+ forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1
+
+ runner_project.destroy
+
+ present runner, with: ::API::Entities::Runner
+ end
+ end
+
+ helpers do
+ def authenticate_delete_runner!(runner)
+ return if current_user.is_admin?
+ forbidden!("Runner is shared") if runner.is_shared?
+ forbidden!("Runner associated with more than one project") if runner.projects.count > 1
+ forbidden!("No access granted") unless user_can_access_runner?(runner)
+ end
+
+ def user_can_access_runner?(runner)
+ current_user.ci_authorized_runners.exists?(runner.id)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/services.rb b/lib/api/v3/services.rb
new file mode 100644
index 00000000000..af0a058f69b
--- /dev/null
+++ b/lib/api/v3/services.rb
@@ -0,0 +1,573 @@
+module API
+ module V3
+ class Services < Grape::API
+ services = {
+ 'asana' => [
+ {
+ required: true,
+ name: :api_key,
+ type: String,
+ desc: 'User API token'
+ },
+ {
+ required: false,
+ name: :restrict_to_branch,
+ type: String,
+ desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches'
+ }
+ ],
+ 'assembla' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The authentication token'
+ },
+ {
+ required: false,
+ name: :subdomain,
+ type: String,
+ desc: 'Subdomain setting'
+ }
+ ],
+ 'bamboo' => [
+ {
+ required: true,
+ name: :bamboo_url,
+ type: String,
+ desc: 'Bamboo root URL like https://bamboo.example.com'
+ },
+ {
+ required: true,
+ name: :build_key,
+ type: String,
+ desc: 'Bamboo build plan key like'
+ },
+ {
+ required: true,
+ name: :username,
+ type: String,
+ desc: 'A user with API access, if applicable'
+ },
+ {
+ required: true,
+ name: :password,
+ type: String,
+ desc: 'Passord of the user'
+ }
+ ],
+ 'bugzilla' => [
+ {
+ required: true,
+ name: :new_issue_url,
+ type: String,
+ desc: 'New issue URL'
+ },
+ {
+ required: true,
+ name: :issues_url,
+ type: String,
+ desc: 'Issues URL'
+ },
+ {
+ required: true,
+ name: :project_url,
+ type: String,
+ desc: 'Project URL'
+ },
+ {
+ required: false,
+ name: :description,
+ type: String,
+ desc: 'Description'
+ },
+ {
+ required: false,
+ name: :title,
+ type: String,
+ desc: 'Title'
+ }
+ ],
+ 'buildkite' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'Buildkite project GitLab token'
+ },
+ {
+ required: true,
+ name: :project_url,
+ type: String,
+ desc: 'The buildkite project URL'
+ },
+ {
+ required: false,
+ name: :enable_ssl_verification,
+ type: Boolean,
+ desc: 'Enable SSL verification for communication'
+ }
+ ],
+ 'builds-email' => [
+ {
+ required: true,
+ name: :recipients,
+ type: String,
+ desc: 'Comma-separated list of recipient email addresses'
+ },
+ {
+ required: false,
+ name: :add_pusher,
+ type: Boolean,
+ desc: 'Add pusher to recipients list'
+ },
+ {
+ required: false,
+ name: :notify_only_broken_builds,
+ type: Boolean,
+ desc: 'Notify only broken builds'
+ }
+ ],
+ 'campfire' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'Campfire token'
+ },
+ {
+ required: false,
+ name: :subdomain,
+ type: String,
+ desc: 'Campfire subdomain'
+ },
+ {
+ required: false,
+ name: :room,
+ type: String,
+ desc: 'Campfire room'
+ }
+ ],
+ 'custom-issue-tracker' => [
+ {
+ required: true,
+ name: :new_issue_url,
+ type: String,
+ desc: 'New issue URL'
+ },
+ {
+ required: true,
+ name: :issues_url,
+ type: String,
+ desc: 'Issues URL'
+ },
+ {
+ required: true,
+ name: :project_url,
+ type: String,
+ desc: 'Project URL'
+ },
+ {
+ required: false,
+ name: :description,
+ type: String,
+ desc: 'Description'
+ },
+ {
+ required: false,
+ name: :title,
+ type: String,
+ desc: 'Title'
+ }
+ ],
+ 'drone-ci' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'Drone CI token'
+ },
+ {
+ required: true,
+ name: :drone_url,
+ type: String,
+ desc: 'Drone CI URL'
+ },
+ {
+ required: false,
+ name: :enable_ssl_verification,
+ type: Boolean,
+ desc: 'Enable SSL verification for communication'
+ }
+ ],
+ 'emails-on-push' => [
+ {
+ required: true,
+ name: :recipients,
+ type: String,
+ desc: 'Comma-separated list of recipient email addresses'
+ },
+ {
+ required: false,
+ name: :disable_diffs,
+ type: Boolean,
+ desc: 'Disable code diffs'
+ },
+ {
+ required: false,
+ name: :send_from_committer_email,
+ type: Boolean,
+ desc: 'Send from committer'
+ }
+ ],
+ 'external-wiki' => [
+ {
+ required: true,
+ name: :external_wiki_url,
+ type: String,
+ desc: 'The URL of the external Wiki'
+ }
+ ],
+ 'flowdock' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'Flowdock token'
+ }
+ ],
+ 'gemnasium' => [
+ {
+ required: true,
+ name: :api_key,
+ type: String,
+ desc: 'Your personal API key on gemnasium.com'
+ },
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: "The project's slug on gemnasium.com"
+ }
+ ],
+ 'hipchat' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The room token'
+ },
+ {
+ required: false,
+ name: :room,
+ type: String,
+ desc: 'The room name or ID'
+ },
+ {
+ required: false,
+ name: :color,
+ type: String,
+ desc: 'The room color'
+ },
+ {
+ required: false,
+ name: :notify,
+ type: Boolean,
+ desc: 'Enable notifications'
+ },
+ {
+ required: false,
+ name: :api_version,
+ type: String,
+ desc: 'Leave blank for default (v2)'
+ },
+ {
+ required: false,
+ name: :server,
+ type: String,
+ desc: 'Leave blank for default. https://hipchat.example.com'
+ }
+ ],
+ 'irker' => [
+ {
+ required: true,
+ name: :recipients,
+ type: String,
+ desc: 'Recipients/channels separated by whitespaces'
+ },
+ {
+ required: false,
+ name: :default_irc_uri,
+ type: String,
+ desc: 'Default: irc://irc.network.net:6697'
+ },
+ {
+ required: false,
+ name: :server_host,
+ type: String,
+ desc: 'Server host. Default localhost'
+ },
+ {
+ required: false,
+ name: :server_port,
+ type: Integer,
+ desc: 'Server port. Default 6659'
+ },
+ {
+ required: false,
+ name: :colorize_messages,
+ type: Boolean,
+ desc: 'Colorize messages'
+ }
+ ],
+ 'jira' => [
+ {
+ required: true,
+ name: :url,
+ type: String,
+ desc: 'The URL to the JIRA project which is being linked to this GitLab project, e.g., https://jira.example.com'
+ },
+ {
+ required: true,
+ name: :project_key,
+ type: String,
+ desc: 'The short identifier for your JIRA project, all uppercase, e.g., PROJ'
+ },
+ {
+ required: false,
+ name: :username,
+ type: String,
+ desc: 'The username of the user created to be used with GitLab/JIRA'
+ },
+ {
+ required: false,
+ name: :password,
+ type: String,
+ desc: 'The password of the user created to be used with GitLab/JIRA'
+ },
+ {
+ required: false,
+ name: :jira_issue_transition_id,
+ type: Integer,
+ desc: 'The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`'
+ }
+ ],
+
+ 'kubernetes' => [
+ {
+ required: true,
+ name: :namespace,
+ type: String,
+ desc: 'The Kubernetes namespace to use'
+ },
+ {
+ required: true,
+ name: :api_url,
+ type: String,
+ desc: 'The URL to the Kubernetes cluster API, e.g., https://kubernetes.example.com'
+ },
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The service token to authenticate against the Kubernetes cluster with'
+ },
+ {
+ required: false,
+ name: :ca_pem,
+ type: String,
+ desc: 'A custom certificate authority bundle to verify the Kubernetes cluster with (PEM format)'
+ },
+ ],
+ 'mattermost-slash-commands' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The Mattermost token'
+ }
+ ],
+ 'slack-slash-commands' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The Slack token'
+ }
+ ],
+ 'pipelines-email' => [
+ {
+ required: true,
+ name: :recipients,
+ type: String,
+ desc: 'Comma-separated list of recipient email addresses'
+ },
+ {
+ required: false,
+ name: :notify_only_broken_builds,
+ type: Boolean,
+ desc: 'Notify only broken builds'
+ }
+ ],
+ 'pivotaltracker' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The Pivotaltracker token'
+ },
+ {
+ required: false,
+ name: :restrict_to_branch,
+ type: String,
+ desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.'
+ }
+ ],
+ 'pushover' => [
+ {
+ required: true,
+ name: :api_key,
+ type: String,
+ desc: 'The application key'
+ },
+ {
+ required: true,
+ name: :user_key,
+ type: String,
+ desc: 'The user key'
+ },
+ {
+ required: true,
+ name: :priority,
+ type: String,
+ desc: 'The priority'
+ },
+ {
+ required: true,
+ name: :device,
+ type: String,
+ desc: 'Leave blank for all active devices'
+ },
+ {
+ required: true,
+ name: :sound,
+ type: String,
+ desc: 'The sound of the notification'
+ }
+ ],
+ 'redmine' => [
+ {
+ required: true,
+ name: :new_issue_url,
+ type: String,
+ desc: 'The new issue URL'
+ },
+ {
+ required: true,
+ name: :project_url,
+ type: String,
+ desc: 'The project URL'
+ },
+ {
+ required: true,
+ name: :issues_url,
+ type: String,
+ desc: 'The issues URL'
+ },
+ {
+ required: false,
+ name: :description,
+ type: String,
+ desc: 'The description of the tracker'
+ }
+ ],
+ 'slack' => [
+ {
+ required: true,
+ name: :webhook,
+ type: String,
+ desc: 'The Slack webhook. e.g. https://hooks.slack.com/services/...'
+ },
+ {
+ required: false,
+ name: :new_issue_url,
+ type: String,
+ desc: 'The user name'
+ },
+ {
+ required: false,
+ name: :channel,
+ type: String,
+ desc: 'The channel name'
+ }
+ ],
+ 'mattermost' => [
+ {
+ required: true,
+ name: :webhook,
+ type: String,
+ desc: 'The Mattermost webhook. e.g. http://mattermost_host/hooks/...'
+ }
+ ],
+ 'teamcity' => [
+ {
+ required: true,
+ name: :teamcity_url,
+ type: String,
+ desc: 'TeamCity root URL like https://teamcity.example.com'
+ },
+ {
+ required: true,
+ name: :build_type,
+ type: String,
+ desc: 'Build configuration ID'
+ },
+ {
+ required: true,
+ name: :username,
+ type: String,
+ desc: 'A user with permissions to trigger a manual build'
+ },
+ {
+ required: true,
+ name: :password,
+ type: String,
+ desc: 'The password of the user'
+ }
+ ]
+ }
+
+ resource :projects do
+ before { authenticate! }
+ before { authorize_admin_project }
+
+ helpers do
+ def service_attributes(service)
+ service.fields.inject([]) do |arr, hash|
+ arr << hash[:name].to_sym
+ end
+ end
+ end
+
+ desc "Delete a service for project"
+ params do
+ requires :service_slug, type: String, values: services.keys, desc: 'The name of the service'
+ end
+ delete ":id/services/:service_slug" do
+ service = user_project.find_or_initialize_service(params[:service_slug].underscore)
+
+ attrs = service_attributes(service).inject({}) do |hash, key|
+ hash.merge!(key => nil)
+ end
+
+ if service.update_attributes(attrs.merge(active: false))
+ status(200)
+ true
+ else
+ render_api_error!('400 Bad Request', 400)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/system_hooks.rb b/lib/api/v3/system_hooks.rb
index 391510b9ee0..5787c06fc12 100644
--- a/lib/api/v3/system_hooks.rb
+++ b/lib/api/v3/system_hooks.rb
@@ -13,6 +13,19 @@ module API
get do
present SystemHook.all, with: ::API::Entities::Hook
end
+
+ desc 'Delete a hook' do
+ success ::API::Entities::Hook
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the system hook'
+ end
+ delete ":id" do
+ hook = SystemHook.find_by(id: params[:id])
+ not_found!('System hook') unless hook
+
+ present hook.destroy, with: ::API::Entities::Hook
+ end
end
end
end
diff --git a/lib/api/v3/tags.rb b/lib/api/v3/tags.rb
index 016e3d86932..6913720d9c5 100644
--- a/lib/api/v3/tags.rb
+++ b/lib/api/v3/tags.rb
@@ -14,6 +14,26 @@ module API
tags = user_project.repository.tags.sort_by(&:name).reverse
present tags, with: ::API::Entities::RepoTag, project: user_project
end
+
+ desc 'Delete a repository tag'
+ params do
+ requires :tag_name, type: String, desc: 'The name of the tag'
+ end
+ delete ":id/repository/tags/:tag_name", requirements: { tag_name: /.+/ } do
+ authorize_push_project
+
+ result = ::Tags::DestroyService.new(user_project, current_user).
+ execute(params[:tag_name])
+
+ if result[:status] == :success
+ status(200)
+ {
+ tag_name: params[:tag_name]
+ }
+ else
+ render_api_error!(result[:message], result[:return_code])
+ end
+ end
end
end
end
diff --git a/lib/api/v3/todos.rb b/lib/api/v3/todos.rb
index 4f9b5fe72a6..e60cb25e57b 100644
--- a/lib/api/v3/todos.rb
+++ b/lib/api/v3/todos.rb
@@ -19,6 +19,8 @@ module API
desc 'Mark all todos as done'
delete do
+ status(200)
+
todos = TodosFinder.new(current_user, params).execute
TodoService.new.mark_todos_as_done(todos, current_user)
end
diff --git a/lib/api/v3/triggers.rb b/lib/api/v3/triggers.rb
new file mode 100644
index 00000000000..4051d4bca8d
--- /dev/null
+++ b/lib/api/v3/triggers.rb
@@ -0,0 +1,30 @@
+module API
+ module V3
+ class Triggers < Grape::API
+ include PaginationParams
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ desc 'Delete a trigger' do
+ success ::API::Entities::Trigger
+ end
+ params do
+ requires :token, type: String, desc: 'The unique token of trigger'
+ end
+ delete ':id/triggers/:token' do
+ authenticate!
+ authorize! :admin_build, user_project
+
+ trigger = user_project.triggers.find_by(token: params[:token].to_s)
+ return not_found!('Trigger') unless trigger
+
+ trigger.destroy
+
+ present trigger, with: ::API::Entities::Trigger
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/users.rb b/lib/api/v3/users.rb
index 7838cdc46a7..14f54731730 100644
--- a/lib/api/v3/users.rb
+++ b/lib/api/v3/users.rb
@@ -92,6 +92,25 @@ module API
present paginate(events), with: ::API::V3::Entities::Event
end
+
+ desc 'Delete an existing SSH key from a specified user. Available only for admins.' do
+ success ::API::Entities::SSHKey
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ requires :key_id, type: Integer, desc: 'The ID of the SSH key'
+ end
+ delete ':id/keys/:key_id' do
+ authenticated_as_admin!
+
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
+
+ key = user.keys.find_by(id: params[:key_id])
+ not_found!('Key') unless key
+
+ present key.destroy, with: ::API::Entities::SSHKey
+ end
end
resource :user do
@@ -111,6 +130,19 @@ module API
get "emails" do
present current_user.emails, with: ::API::Entities::Email
end
+
+ desc 'Delete an SSH key from the currently authenticated user' do
+ success ::API::Entities::SSHKey
+ end
+ params do
+ requires :key_id, type: Integer, desc: 'The ID of the SSH key'
+ end
+ delete "keys/:key_id" do
+ key = current_user.keys.find_by(id: params[:key_id])
+ not_found!('Key') unless key
+
+ present key.destroy, with: ::API::Entities::SSHKey
+ end
end
end
end
diff --git a/lib/api/v3/variables.rb b/lib/api/v3/variables.rb
new file mode 100644
index 00000000000..0f55a14fb28
--- /dev/null
+++ b/lib/api/v3/variables.rb
@@ -0,0 +1,29 @@
+module API
+ module V3
+ class Variables < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+ before { authorize! :admin_build, user_project }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+
+ resource :projects do
+ desc 'Delete an existing variable from a project' do
+ success ::API::Entities::Variable
+ end
+ params do
+ requires :key, type: String, desc: 'The key of the variable'
+ end
+ delete ':id/variables/:key' do
+ variable = user_project.variables.find_by(key: params[:key])
+ not_found!('Variable') unless variable
+
+ present variable.destroy, with: ::API::Entities::Variable
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index f623b1dfe9f..77e5d54c225 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -1,5 +1,4 @@
module API
- # Projects variables API
class Variables < Grape::API
include PaginationParams
@@ -81,10 +80,9 @@ module API
end
delete ':id/variables/:key' do
variable = user_project.variables.find_by(key: params[:key])
+ not_found!('Variable') unless variable
- return not_found!('Variable') unless variable
-
- present variable.destroy, with: Entities::Variable
+ variable.destroy
end
end
end
diff --git a/lib/banzai/filter/image_link_filter.rb b/lib/banzai/filter/image_link_filter.rb
index f0fb6084a35..651b55523c0 100644
--- a/lib/banzai/filter/image_link_filter.rb
+++ b/lib/banzai/filter/image_link_filter.rb
@@ -8,11 +8,6 @@ module Banzai
# of the anchor, and then replace the img with the link-wrapped version.
def call
doc.xpath('descendant-or-self::img[not(ancestor::a)]').each do |img|
- div = doc.document.create_element(
- 'div',
- class: 'image-container'
- )
-
link = doc.document.create_element(
'a',
class: 'no-attachment-icon',
@@ -22,9 +17,7 @@ module Banzai
link.children = img.clone
- div.children = link
-
- img.replace(div)
+ img.replace(link)
end
doc
diff --git a/lib/bitbucket/error/unauthorized.rb b/lib/bitbucket/error/unauthorized.rb
index 5e2eb57bb0e..efe10542f19 100644
--- a/lib/bitbucket/error/unauthorized.rb
+++ b/lib/bitbucket/error/unauthorized.rb
@@ -1,6 +1,5 @@
module Bitbucket
module Error
- class Unauthorized < StandardError
- end
+ Unauthorized = Class.new(StandardError)
end
end
diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb
index 8b939663ffd..b51e76d93f2 100644
--- a/lib/ci/api/builds.rb
+++ b/lib/ci/api/builds.rb
@@ -167,7 +167,10 @@ module Ci
build.artifacts_file = artifacts
build.artifacts_metadata = metadata
- build.artifacts_expire_in = params['expire_in']
+ build.artifacts_expire_in =
+ params['expire_in'] ||
+ Gitlab::CurrentSettings.current_application_settings
+ .default_artifacts_expire_in
if build.save
present(build, with: Entities::BuildDetails)
@@ -214,6 +217,7 @@ module Ci
build = Ci::Build.find_by_id(params[:id])
authenticate_build!(build)
+ status(200)
build.erase_artifacts!
end
end
diff --git a/lib/ci/api/runners.rb b/lib/ci/api/runners.rb
index 2a611a67eaf..c1fd959ef14 100644
--- a/lib/ci/api/runners.rb
+++ b/lib/ci/api/runners.rb
@@ -8,6 +8,8 @@ module Ci
end
delete "delete" do
authenticate_runner!
+
+ status(200)
Ci::Runner.find_by_token(params[:token]).destroy
end
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index 649ee4d018b..e390919ae1d 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -1,6 +1,6 @@
module Ci
class GitlabCiYamlProcessor
- class ValidationError < StandardError; end
+ ValidationError = Class.new(StandardError)
include Gitlab::Ci::Config::Entry::LegacyValidationHelpers
diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb
index 9ece84cc469..dd864eea3fa 100644
--- a/lib/extracts_path.rb
+++ b/lib/extracts_path.rb
@@ -2,7 +2,7 @@
# file path string when combined in a request parameter
module ExtractsPath
# Raised when given an invalid file path
- class InvalidPathError < StandardError; end
+ InvalidPathError = Class.new(StandardError)
# Given a string containing both a Git tree-ish, such as a branch or tag, and
# a filesystem path joined by forward slashes, attempts to separate the two.
diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb
index 3b210eeda9d..8c28009b9c6 100644
--- a/lib/gitlab/access.rb
+++ b/lib/gitlab/access.rb
@@ -5,7 +5,7 @@
#
module Gitlab
module Access
- class AccessDeniedError < StandardError; end
+ AccessDeniedError = Class.new(StandardError)
NO_ACCESS = 0
GUEST = 10
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 89db6c3da46..0a5abc92190 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -1,6 +1,6 @@
module Gitlab
module Auth
- class MissingPersonalTokenError < StandardError; end
+ MissingPersonalTokenError = Class.new(StandardError)
SCOPES = [:api, :read_user].freeze
DEFAULT_SCOPES = [:api].freeze
diff --git a/lib/gitlab/ci/build/artifacts/metadata.rb b/lib/gitlab/ci/build/artifacts/metadata.rb
index cd2e83b4c27..a375ccbece0 100644
--- a/lib/gitlab/ci/build/artifacts/metadata.rb
+++ b/lib/gitlab/ci/build/artifacts/metadata.rb
@@ -6,7 +6,7 @@ module Gitlab
module Build
module Artifacts
class Metadata
- class ParserError < StandardError; end
+ ParserError = Class.new(StandardError)
VERSION_PATTERN = /^[\w\s]+(\d+\.\d+\.\d+)/
INVALID_PATH_PATTERN = %r{(^\.?\.?/)|(/\.?\.?/)}
diff --git a/lib/gitlab/ci/config/entry/factory.rb b/lib/gitlab/ci/config/entry/factory.rb
index 9f5e393d191..6be8288748f 100644
--- a/lib/gitlab/ci/config/entry/factory.rb
+++ b/lib/gitlab/ci/config/entry/factory.rb
@@ -6,7 +6,7 @@ module Gitlab
# Factory class responsible for fabricating entry objects.
#
class Factory
- class InvalidFactory < StandardError; end
+ InvalidFactory = Class.new(StandardError)
def initialize(entry)
@entry = entry
diff --git a/lib/gitlab/ci/config/entry/node.rb b/lib/gitlab/ci/config/entry/node.rb
index 5eef2868cd6..55a5447ab51 100644
--- a/lib/gitlab/ci/config/entry/node.rb
+++ b/lib/gitlab/ci/config/entry/node.rb
@@ -6,7 +6,7 @@ module Gitlab
# Base abstract class for each configuration entry node.
#
class Node
- class InvalidError < StandardError; end
+ InvalidError = Class.new(StandardError)
attr_reader :config, :metadata
attr_accessor :key, :parent, :description
diff --git a/lib/gitlab/ci/config/loader.rb b/lib/gitlab/ci/config/loader.rb
index dbf6eb0edbe..e7d9f6a7761 100644
--- a/lib/gitlab/ci/config/loader.rb
+++ b/lib/gitlab/ci/config/loader.rb
@@ -2,7 +2,7 @@ module Gitlab
module Ci
class Config
class Loader
- class FormatError < StandardError; end
+ FormatError = Class.new(StandardError)
def initialize(config)
@config = YAML.safe_load(config, [Symbol], [], true)
diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb
index d80bc748209..75a213ef752 100644
--- a/lib/gitlab/conflict/file.rb
+++ b/lib/gitlab/conflict/file.rb
@@ -4,8 +4,7 @@ module Gitlab
include Gitlab::Routing.url_helpers
include IconsHelper
- class MissingResolution < ResolutionError
- end
+ MissingResolution = Class.new(ResolutionError)
CONTEXT_LINES = 3
diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb
index fa5bd4649d4..990b719ecfd 100644
--- a/lib/gitlab/conflict/file_collection.rb
+++ b/lib/gitlab/conflict/file_collection.rb
@@ -1,8 +1,7 @@
module Gitlab
module Conflict
class FileCollection
- class ConflictSideMissing < StandardError
- end
+ ConflictSideMissing = Class.new(StandardError)
attr_reader :merge_request, :our_commit, :their_commit
diff --git a/lib/gitlab/conflict/parser.rb b/lib/gitlab/conflict/parser.rb
index ddd657903fb..d3524c338ee 100644
--- a/lib/gitlab/conflict/parser.rb
+++ b/lib/gitlab/conflict/parser.rb
@@ -1,25 +1,15 @@
module Gitlab
module Conflict
class Parser
- class UnresolvableError < StandardError
- end
-
- class UnmergeableFile < UnresolvableError
- end
-
- class UnsupportedEncoding < UnresolvableError
- end
+ UnresolvableError = Class.new(StandardError)
+ UnmergeableFile = Class.new(UnresolvableError)
+ UnsupportedEncoding = Class.new(UnresolvableError)
# Recoverable errors - the conflict can be resolved in an editor, but not with
# sections.
- class ParserError < StandardError
- end
-
- class UnexpectedDelimiter < ParserError
- end
-
- class MissingEndDelimiter < ParserError
- end
+ ParserError = Class.new(StandardError)
+ UnexpectedDelimiter = Class.new(ParserError)
+ MissingEndDelimiter = Class.new(ParserError)
def parse(text, our_path:, their_path:, parent_file: nil)
raise UnmergeableFile if text.blank? # Typically a binary file
diff --git a/lib/gitlab/conflict/resolution_error.rb b/lib/gitlab/conflict/resolution_error.rb
index a0f2006bc24..0b61256b35a 100644
--- a/lib/gitlab/conflict/resolution_error.rb
+++ b/lib/gitlab/conflict/resolution_error.rb
@@ -1,6 +1,5 @@
module Gitlab
module Conflict
- class ResolutionError < StandardError
- end
+ ResolutionError = Class.new(StandardError)
end
end
diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb
index b64db5d01ae..ec0529b5a4b 100644
--- a/lib/gitlab/email/receiver.rb
+++ b/lib/gitlab/email/receiver.rb
@@ -4,19 +4,19 @@ require_dependency 'gitlab/email/handler'
# Inspired in great part by Discourse's Email::Receiver
module Gitlab
module Email
- class ProcessingError < StandardError; end
- class EmailUnparsableError < ProcessingError; end
- class SentNotificationNotFoundError < ProcessingError; end
- class ProjectNotFound < ProcessingError; end
- class EmptyEmailError < ProcessingError; end
- class AutoGeneratedEmailError < ProcessingError; end
- class UserNotFoundError < ProcessingError; end
- class UserBlockedError < ProcessingError; end
- class UserNotAuthorizedError < ProcessingError; end
- class NoteableNotFoundError < ProcessingError; end
- class InvalidNoteError < ProcessingError; end
- class InvalidIssueError < ProcessingError; end
- class UnknownIncomingEmail < ProcessingError; end
+ ProcessingError = Class.new(StandardError)
+ EmailUnparsableError = Class.new(ProcessingError)
+ SentNotificationNotFoundError = Class.new(ProcessingError)
+ ProjectNotFound = Class.new(ProcessingError)
+ EmptyEmailError = Class.new(ProcessingError)
+ AutoGeneratedEmailError = Class.new(ProcessingError)
+ UserNotFoundError = Class.new(ProcessingError)
+ UserBlockedError = Class.new(ProcessingError)
+ UserNotAuthorizedError = Class.new(ProcessingError)
+ NoteableNotFoundError = Class.new(ProcessingError)
+ InvalidNoteError = Class.new(ProcessingError)
+ InvalidIssueError = Class.new(ProcessingError)
+ UnknownIncomingEmail = Class.new(ProcessingError)
class Receiver
def initialize(raw)
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index b742d9e1e4b..e56eb0d3beb 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -93,163 +93,6 @@ module Gitlab
commit_id: sha,
)
end
-
- # Commit file in repository and return commit sha
- #
- # options should contain next structure:
- # file: {
- # content: 'Lorem ipsum...',
- # path: 'documents/story.txt',
- # update: true
- # },
- # author: {
- # email: 'user@example.com',
- # name: 'Test User',
- # time: Time.now
- # },
- # committer: {
- # email: 'user@example.com',
- # name: 'Test User',
- # time: Time.now
- # },
- # commit: {
- # message: 'Wow such commit',
- # branch: 'master',
- # update_ref: false
- # }
- #
- # rubocop:disable Metrics/AbcSize
- # rubocop:disable Metrics/CyclomaticComplexity
- # rubocop:disable Metrics/PerceivedComplexity
- def commit(repository, options, action = :add)
- file = options[:file]
- update = file[:update].nil? ? true : file[:update]
- author = options[:author]
- committer = options[:committer]
- commit = options[:commit]
- repo = repository.rugged
- ref = commit[:branch]
- update_ref = commit[:update_ref].nil? ? true : commit[:update_ref]
- parents = []
- mode = 0o100644
-
- unless ref.start_with?('refs/')
- ref = 'refs/heads/' + ref
- end
-
- path_name = Gitlab::Git::PathHelper.normalize_path(file[:path])
- # Abort if any invalid characters remain (e.g. ../foo)
- raise Gitlab::Git::Repository::InvalidBlobName.new("Invalid path") if path_name.each_filename.to_a.include?('..')
-
- filename = path_name.to_s
- index = repo.index
-
- unless repo.empty?
- rugged_ref = repo.references[ref]
- raise Gitlab::Git::Repository::InvalidRef.new("Invalid branch name") unless rugged_ref
- last_commit = rugged_ref.target
- index.read_tree(last_commit.tree)
- parents = [last_commit]
- end
-
- if action == :remove
- index.remove(filename)
- else
- file_entry = index.get(filename)
-
- if action == :rename
- old_path_name = Gitlab::Git::PathHelper.normalize_path(file[:previous_path])
- old_filename = old_path_name.to_s
- file_entry = index.get(old_filename)
- index.remove(old_filename) unless file_entry.blank?
- end
-
- if file_entry
- raise Gitlab::Git::Repository::InvalidBlobName.new("Filename already exists; update not allowed") unless update
-
- # Preserve the current file mode if one is available
- mode = file_entry[:mode] if file_entry[:mode]
- end
-
- content = file[:content]
- detect = CharlockHolmes::EncodingDetector.new.detect(content) if content
-
- unless detect && detect[:type] == :binary
- # When writing to the repo directly as we are doing here,
- # the `core.autocrlf` config isn't taken into account.
- content.gsub!("\r\n", "\n") if repository.autocrlf
- end
-
- oid = repo.write(content, :blob)
- index.add(path: filename, oid: oid, mode: mode)
- end
-
- opts = {}
- opts[:tree] = index.write_tree(repo)
- opts[:author] = author
- opts[:committer] = committer
- opts[:message] = commit[:message]
- opts[:parents] = parents
- opts[:update_ref] = ref if update_ref
-
- Rugged::Commit.create(repo, opts)
- end
- # rubocop:enable Metrics/AbcSize
- # rubocop:enable Metrics/CyclomaticComplexity
- # rubocop:enable Metrics/PerceivedComplexity
-
- # Remove file from repository and return commit sha
- #
- # options should contain next structure:
- # file: {
- # path: 'documents/story.txt'
- # },
- # author: {
- # email: 'user@example.com',
- # name: 'Test User',
- # time: Time.now
- # },
- # committer: {
- # email: 'user@example.com',
- # name: 'Test User',
- # time: Time.now
- # },
- # commit: {
- # message: 'Remove FILENAME',
- # branch: 'master'
- # }
- #
- def remove(repository, options)
- commit(repository, options, :remove)
- end
-
- # Rename file from repository and return commit sha
- #
- # options should contain next structure:
- # file: {
- # previous_path: 'documents/old_story.txt'
- # path: 'documents/story.txt'
- # content: 'Lorem ipsum...',
- # update: true
- # },
- # author: {
- # email: 'user@example.com',
- # name: 'Test User',
- # time: Time.now
- # },
- # committer: {
- # email: 'user@example.com',
- # name: 'Test User',
- # time: Time.now
- # },
- # commit: {
- # message: 'Rename FILENAME',
- # branch: 'master'
- # }
- #
- def rename(repository, options)
- commit(repository, options, :rename)
- end
end
def initialize(options)
diff --git a/lib/gitlab/git/diff.rb b/lib/gitlab/git/diff.rb
index d6b3b5705a9..2a017c93f57 100644
--- a/lib/gitlab/git/diff.rb
+++ b/lib/gitlab/git/diff.rb
@@ -2,7 +2,7 @@
module Gitlab
module Git
class Diff
- class TimeoutError < StandardError; end
+ TimeoutError = Class.new(StandardError)
include Gitlab::Git::EncodingHelper
# Diff properties
diff --git a/lib/gitlab/git/index.rb b/lib/gitlab/git/index.rb
new file mode 100644
index 00000000000..af1744c9c46
--- /dev/null
+++ b/lib/gitlab/git/index.rb
@@ -0,0 +1,126 @@
+module Gitlab
+ module Git
+ class Index
+ DEFAULT_MODE = 0o100644
+
+ attr_reader :repository, :raw_index
+
+ def initialize(repository)
+ @repository = repository
+ @raw_index = repository.rugged.index
+ end
+
+ delegate :read_tree, :get, to: :raw_index
+
+ def write_tree
+ raw_index.write_tree(repository.rugged)
+ end
+
+ def dir_exists?(path)
+ raw_index.find { |entry| entry[:path].start_with?("#{path}/") }
+ end
+
+ def create(options)
+ options = normalize_options(options)
+
+ file_entry = get(options[:file_path])
+ if file_entry
+ raise Gitlab::Git::Repository::InvalidBlobName.new("Filename already exists")
+ end
+
+ add_blob(options)
+ end
+
+ def create_dir(options)
+ options = normalize_options(options)
+
+ file_entry = get(options[:file_path])
+ if file_entry
+ raise Gitlab::Git::Repository::InvalidBlobName.new("Directory already exists as a file")
+ end
+
+ if dir_exists?(options[:file_path])
+ raise Gitlab::Git::Repository::InvalidBlobName.new("Directory already exists")
+ end
+
+ options = options.dup
+ options[:file_path] += '/.gitkeep'
+ options[:content] = ''
+
+ add_blob(options)
+ end
+
+ def update(options)
+ options = normalize_options(options)
+
+ file_entry = get(options[:file_path])
+ unless file_entry
+ raise Gitlab::Git::Repository::InvalidBlobName.new("File doesn't exist")
+ end
+
+ add_blob(options, mode: file_entry[:mode])
+ end
+
+ def move(options)
+ options = normalize_options(options)
+
+ file_entry = get(options[:previous_path])
+ unless file_entry
+ raise Gitlab::Git::Repository::InvalidBlobName.new("File doesn't exist")
+ end
+
+ raw_index.remove(options[:previous_path])
+
+ add_blob(options, mode: file_entry[:mode])
+ end
+
+ def delete(options)
+ options = normalize_options(options)
+
+ file_entry = get(options[:file_path])
+ unless file_entry
+ raise Gitlab::Git::Repository::InvalidBlobName.new("File doesn't exist")
+ end
+
+ raw_index.remove(options[:file_path])
+ end
+
+ private
+
+ def normalize_options(options)
+ options = options.dup
+ options[:file_path] = normalize_path(options[:file_path]) if options[:file_path]
+ options[:previous_path] = normalize_path(options[:previous_path]) if options[:previous_path]
+ options
+ end
+
+ def normalize_path(path)
+ pathname = Gitlab::Git::PathHelper.normalize_path(path.dup)
+
+ if pathname.each_filename.include?('..')
+ raise Gitlab::Git::Repository::InvalidBlobName.new('Invalid path')
+ end
+
+ pathname.to_s
+ end
+
+ def add_blob(options, mode: nil)
+ content = options[:content]
+ content = Base64.decode64(content) if options[:encoding] == 'base64'
+
+ detect = CharlockHolmes::EncodingDetector.new.detect(content)
+ unless detect && detect[:type] == :binary
+ # When writing to the repo directly as we are doing here,
+ # the `core.autocrlf` config isn't taken into account.
+ content.gsub!("\r\n", "\n") if repository.autocrlf
+ end
+
+ oid = repository.rugged.write(content, :blob)
+
+ raw_index.add(path: options[:file_path], oid: oid, mode: mode || DEFAULT_MODE)
+ rescue Rugged::IndexError => e
+ raise Gitlab::Git::Repository::InvalidBlobName.new(e.message)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 4b6ad8037ce..0e9b812ffdd 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -10,9 +10,9 @@ module Gitlab
SEARCH_CONTEXT_LINES = 3
- class NoRepository < StandardError; end
- class InvalidBlobName < StandardError; end
- class InvalidRef < StandardError; end
+ NoRepository = Class.new(StandardError)
+ InvalidBlobName = Class.new(StandardError)
+ InvalidRef = Class.new(StandardError)
# Full path to repo
attr_reader :path
@@ -324,24 +324,30 @@ module Gitlab
end
def log_by_shell(sha, options)
- cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{path} log)
- cmd += %W(-n #{options[:limit].to_i})
- cmd += %w(--format=%H)
- cmd += %W(--skip=#{options[:offset].to_i})
- cmd += %w(--follow) if options[:follow]
- cmd += %w(--no-merges) if options[:skip_merges]
- cmd += %W(--after=#{options[:after].iso8601}) if options[:after]
- cmd += %W(--before=#{options[:before].iso8601}) if options[:before]
- cmd += [sha]
- cmd += %W(-- #{options[:path]}) if options[:path].present?
-
- raw_output = IO.popen(cmd) {|io| io.read }
-
- log = raw_output.lines.map do |c|
- Rugged::Commit.new(rugged, c.strip)
- end
+ limit = options[:limit].to_i
+ offset = options[:offset].to_i
+ use_follow_flag = options[:follow] && options[:path].present?
+
+ # We will perform the offset in Ruby because --follow doesn't play well with --skip.
+ # See: https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
+ offset_in_ruby = use_follow_flag && options[:offset].present?
+ limit += offset if offset_in_ruby
+
+ cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} log]
+ cmd << "--max-count=#{limit}"
+ cmd << '--format=%H'
+ cmd << "--skip=#{offset}" unless offset_in_ruby
+ cmd << '--follow' if use_follow_flag
+ cmd << '--no-merges' if options[:skip_merges]
+ cmd << "--after=#{options[:after].iso8601}" if options[:after]
+ cmd << "--before=#{options[:before].iso8601}" if options[:before]
+ cmd << sha
+ cmd += %W[-- #{options[:path]}] if options[:path].present?
- log.is_a?(Array) ? log : []
+ raw_output = IO.popen(cmd) { |io| io.read }
+ lines = offset_in_ruby ? raw_output.lines.drop(offset) : raw_output.lines
+
+ lines.map! { |c| Rugged::Commit.new(rugged, c.strip) }
end
def sha_from_ref(ref)
@@ -837,57 +843,6 @@ module Gitlab
rugged.config['core.autocrlf'] = AUTOCRLF_VALUES.invert[value]
end
- # Create a new directory with a .gitkeep file. Creates
- # all required nested directories (i.e. mkdir -p behavior)
- #
- # options should contain next structure:
- # author: {
- # email: 'user@example.com',
- # name: 'Test User',
- # time: Time.now
- # },
- # committer: {
- # email: 'user@example.com',
- # name: 'Test User',
- # time: Time.now
- # },
- # commit: {
- # message: 'Wow such commit',
- # branch: 'master',
- # update_ref: false
- # }
- def mkdir(path, options = {})
- # Check if this directory exists; if it does, then don't bother
- # adding .gitkeep file.
- ref = options[:commit][:branch]
- path = Gitlab::Git::PathHelper.normalize_path(path).to_s
- rugged_ref = rugged.ref(ref)
-
- raise InvalidRef.new("Invalid ref") if rugged_ref.nil?
-
- target_commit = rugged_ref.target
-
- raise InvalidRef.new("Invalid target commit") if target_commit.nil?
-
- entry = tree_entry(target_commit, path)
-
- if entry
- if entry[:type] == :blob
- raise InvalidBlobName.new("Directory already exists as a file")
- else
- raise InvalidBlobName.new("Directory already exists")
- end
- end
-
- options[:file] = {
- content: '',
- path: "#{path}/.gitkeep",
- update: true
- }
-
- Gitlab::Git::Blob.commit(self, options)
- end
-
# Returns result like "git ls-files" , recursive and full file path
#
# Ex.
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
new file mode 100644
index 00000000000..b981a629fb0
--- /dev/null
+++ b/lib/gitlab/gitaly_client.rb
@@ -0,0 +1,29 @@
+require 'gitaly'
+
+module Gitlab
+ module GitalyClient
+ def self.gitaly_address
+ if Gitlab.config.gitaly.socket_path
+ "unix://#{Gitlab.config.gitaly.socket_path}"
+ end
+ end
+
+ def self.channel
+ return @channel if defined?(@channel)
+
+ @channel =
+ if enabled?
+ # NOTE: Gitaly currently runs on a Unix socket, so permissions are
+ # handled using the file system and no additional authentication is
+ # required (therefore the :this_channel_is_insecure flag)
+ GRPC::Core::Channel.new(gitaly_address, {}, :this_channel_is_insecure)
+ else
+ nil
+ end
+ end
+
+ def self.enabled?
+ gitaly_address.present?
+ end
+ end
+end
diff --git a/lib/gitlab/gitaly_client/notifications.rb b/lib/gitlab/gitaly_client/notifications.rb
new file mode 100644
index 00000000000..b827a56207f
--- /dev/null
+++ b/lib/gitlab/gitaly_client/notifications.rb
@@ -0,0 +1,17 @@
+module Gitlab
+ module GitalyClient
+ class Notifications
+ attr_accessor :stub
+
+ def initialize
+ @stub = Gitaly::Notifications::Stub.new(nil, nil, channel_override: GitalyClient.channel)
+ end
+
+ def post_receive(repo_path)
+ repository = Gitaly::Repository.new(path: repo_path)
+ request = Gitaly::PostReceiveRequest.new(repository: repository)
+ stub.post_receive(request)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 101b1b80c1e..9c384069661 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -1,7 +1,7 @@
module Gitlab
module GonHelper
def add_gon_variables
- gon.api_version = API::API.version
+ gon.api_version = 'v3' # v4 Is not officially released yet, therefore can't be considered as "frozen"
gon.default_avatar_url = URI.join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
gon.max_file_size = current_application_settings.max_attachment_size
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb
index f1d1af8eee5..8b327cfc226 100644
--- a/lib/gitlab/import_export.rb
+++ b/lib/gitlab/import_export.rb
@@ -35,7 +35,7 @@ module Gitlab
end
def export_filename(project:)
- basename = "#{Time.now.strftime('%Y-%m-%d_%H-%M-%3N')}_#{project.namespace.full_path}_#{project.path}"
+ basename = "#{Time.now.strftime('%Y-%m-%d_%H-%M-%3N')}_#{project.full_path.tr('/', '_')}"
"#{basename[0..FILENAME_LIMIT]}_export.tar.gz"
end
diff --git a/lib/gitlab/import_export/error.rb b/lib/gitlab/import_export/error.rb
index e341c4d9cf8..788eedf2686 100644
--- a/lib/gitlab/import_export/error.rb
+++ b/lib/gitlab/import_export/error.rb
@@ -1,5 +1,5 @@
module Gitlab
module ImportExport
- class Error < StandardError; end
+ Error = Class.new(StandardError)
end
end
diff --git a/lib/gitlab/middleware/webpack_proxy.rb b/lib/gitlab/middleware/webpack_proxy.rb
index 3fe32adeade..6105d165810 100644
--- a/lib/gitlab/middleware/webpack_proxy.rb
+++ b/lib/gitlab/middleware/webpack_proxy.rb
@@ -8,16 +8,16 @@ module Gitlab
@proxy_host = opts.fetch(:proxy_host, 'localhost')
@proxy_port = opts.fetch(:proxy_port, 3808)
@proxy_path = opts[:proxy_path] if opts[:proxy_path]
- super(app, opts)
+
+ super(app, backend: "http://#{@proxy_host}:#{@proxy_port}", **opts)
end
def perform_request(env)
- unless @proxy_path && env['PATH_INFO'].start_with?("/#{@proxy_path}")
- return @app.call(env)
+ if @proxy_path && env['PATH_INFO'].start_with?("/#{@proxy_path}")
+ super(env)
+ else
+ @app.call(env)
end
-
- env['HTTP_HOST'] = "#{@proxy_host}:#{@proxy_port}"
- super(env)
end
end
end
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index 95d2f559588..fcf51b7fc5b 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -5,7 +5,7 @@
#
module Gitlab
module OAuth
- class SignupDisabledError < StandardError; end
+ SignupDisabledError = Class.new(StandardError)
class User
attr_accessor :auth_hash, :gl_user
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index c77fe2d8bdc..5e5f5ff1589 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -5,17 +5,18 @@ module Gitlab
# The namespace regex is used in Javascript to validate usernames in the "Register" form. However, Javascript
# does not support the negative lookbehind assertion (?<!) that disallows usernames ending in `.git` and `.atom`.
# Since this is a non-trivial problem to solve in Javascript (heavily complicate the regex, modify view code to
- # allow non-regex validatiions, etc), `NAMESPACE_REGEX_STR_SIMPLE` serves as a Javascript-compatible version of
+ # allow non-regex validatiions, etc), `NAMESPACE_REGEX_STR_JS` serves as a Javascript-compatible version of
# `NAMESPACE_REGEX_STR`, with the negative lookbehind assertion removed. This means that the client-side validation
# will pass for usernames ending in `.atom` and `.git`, but will be caught by the server-side validation.
PATH_REGEX_STR = '[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*'.freeze
- NAMESPACE_REGEX_STR_SIMPLE = PATH_REGEX_STR + '[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'.freeze
- NAMESPACE_REGEX_STR = '(?:' + NAMESPACE_REGEX_STR_SIMPLE + ')(?<!\.git|\.atom)'.freeze
- PROJECT_REGEX_STR = PATH_REGEX_STR + '(?<!\.git|\.atom)'.freeze
+ NAMESPACE_REGEX_STR_JS = PATH_REGEX_STR + '[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'.freeze
+ NO_SUFFIX_REGEX_STR = '(?<!\.git|\.atom)'.freeze
+ NAMESPACE_REGEX_STR = "(?:#{NAMESPACE_REGEX_STR_JS})#{NO_SUFFIX_REGEX_STR}".freeze
+ PROJECT_REGEX_STR = "(?:#{PATH_REGEX_STR})#{NO_SUFFIX_REGEX_STR}".freeze
# Same as NAMESPACE_REGEX_STR but allows `/` in the path.
# So `group/subgroup` will match this regex but not NAMESPACE_REGEX_STR
- NAMESPACE_REF_REGEX_STR = '(?:[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.\/]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_])(?<!\.git|\.atom)'.freeze
+ FULL_NAMESPACE_REGEX_STR = "(?:#{NAMESPACE_REGEX_STR}/)*#{NAMESPACE_REGEX_STR}".freeze
def namespace_regex
@namespace_regex ||= /\A#{NAMESPACE_REGEX_STR}\z/.freeze
diff --git a/lib/gitlab/route_map.rb b/lib/gitlab/route_map.rb
index 72d00abfcc2..36791fae60f 100644
--- a/lib/gitlab/route_map.rb
+++ b/lib/gitlab/route_map.rb
@@ -1,6 +1,6 @@
module Gitlab
class RouteMap
- class FormatError < StandardError; end
+ FormatError = Class.new(StandardError)
def initialize(data)
begin
diff --git a/lib/gitlab/serializer/pagination.rb b/lib/gitlab/serializer/pagination.rb
index bf2c0acc729..9c92b83dddc 100644
--- a/lib/gitlab/serializer/pagination.rb
+++ b/lib/gitlab/serializer/pagination.rb
@@ -1,7 +1,7 @@
module Gitlab
module Serializer
class Pagination
- class InvalidResourceError < StandardError; end
+ InvalidResourceError = Class.new(StandardError)
include ::API::Helpers::Pagination
def initialize(request, response)
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index 7374d2bc8b8..da8d8ddb8ed 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -2,7 +2,7 @@ require 'securerandom'
module Gitlab
class Shell
- class Error < StandardError; end
+ Error = Class.new(StandardError)
KeyAdder = Struct.new(:io) do
def add_key(id, key)
diff --git a/lib/gitlab/template/finders/repo_template_finder.rb b/lib/gitlab/template/finders/repo_template_finder.rb
index 22c39436cb2..cb7957e2af9 100644
--- a/lib/gitlab/template/finders/repo_template_finder.rb
+++ b/lib/gitlab/template/finders/repo_template_finder.rb
@@ -4,7 +4,7 @@ module Gitlab
module Finders
class RepoTemplateFinder < BaseTemplateFinder
# Raised when file is not found
- class FileNotFoundError < StandardError; end
+ FileNotFoundError = Class.new(StandardError)
def initialize(project, base_dir, extension, categories = {})
@categories = categories
diff --git a/lib/gitlab/update_path_error.rb b/lib/gitlab/update_path_error.rb
index ce14cc887d0..8947ecfb92e 100644
--- a/lib/gitlab/update_path_error.rb
+++ b/lib/gitlab/update_path_error.rb
@@ -1,3 +1,3 @@
module Gitlab
- class UpdatePathError < StandardError; end
+ UpdatePathError = Class.new(StandardError)
end
diff --git a/lib/mattermost/client.rb b/lib/mattermost/client.rb
index 29b10e2afce..3d60618006c 100644
--- a/lib/mattermost/client.rb
+++ b/lib/mattermost/client.rb
@@ -1,5 +1,5 @@
module Mattermost
- class ClientError < Mattermost::Error; end
+ ClientError = Class.new(Mattermost::Error)
class Client
attr_reader :user
diff --git a/lib/mattermost/error.rb b/lib/mattermost/error.rb
index 014df175be0..dee6deb7974 100644
--- a/lib/mattermost/error.rb
+++ b/lib/mattermost/error.rb
@@ -1,3 +1,3 @@
module Mattermost
- class Error < StandardError; end
+ Error = Class.new(StandardError)
end
diff --git a/lib/mattermost/session.rb b/lib/mattermost/session.rb
index d4b4ba97f8c..688a79c0441 100644
--- a/lib/mattermost/session.rb
+++ b/lib/mattermost/session.rb
@@ -5,7 +5,7 @@ module Mattermost
end
end
- class ConnectionError < Mattermost::Error; end
+ ConnectionError = Class.new(Mattermost::Error)
# This class' prime objective is to obtain a session token on a Mattermost
# instance with SSO configured where this GitLab instance is the provider.