debian-mirror-gitlab/lib/api/commits.rb

527 lines
20 KiB
Ruby
Raw Normal View History

2018-12-05 23:21:45 +05:30
# frozen_string_literal: true
2014-09-02 18:07:02 +05:30
require 'mime/types'
module API
2021-01-03 14:25:43 +05:30
class Commits < ::API::Base
2017-08-17 22:00:37 +05:30
include PaginationParams
2021-01-29 00:20:46 +05:30
feature_category :source_code_management
2019-09-30 21:07:59 +05:30
before do
require_repository_enabled!
2023-01-13 00:05:48 +05:30
authorize! :read_code, user_project
2022-08-27 11:52:29 +05:30
verify_pagination_params!
2019-09-30 21:07:59 +05:30
end
2014-09-02 18:07:02 +05:30
2018-11-18 11:00:15 +05:30
helpers do
def user_access
2020-10-24 23:57:45 +05:30
@user_access ||= Gitlab::UserAccess.new(current_user, container: user_project)
2018-11-18 11:00:15 +05:30
end
def authorize_push_to_branch!(branch)
unless user_access.can_push_to_branch?(branch)
forbidden!("You are not allowed to push into this branch")
end
end
end
2016-11-03 12:29:30 +05:30
params do
2023-01-13 00:05:48 +05:30
requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project'
2016-11-03 12:29:30 +05:30
end
2021-12-11 22:18:48 +05:30
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS, urgency: :low do
2016-11-03 12:29:30 +05:30
desc 'Get a project repository commits' do
2023-01-13 00:05:48 +05:30
success code: 200, model: Entities::Commit
tags %w[commits]
is_array true
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 404, message: 'Not found' }
]
2016-11-03 12:29:30 +05:30
end
params do
2023-01-13 00:05:48 +05:30
optional :ref_name,
type: String,
desc: 'The name of a repository branch or tag, if not given the default branch is used',
documentation: { example: 'v1.1.0' }
optional :since,
type: DateTime,
desc: 'Only commits after or on this date will be returned',
documentation: { example: '2021-09-20T11:50:22.001' }
optional :until,
type: DateTime,
desc: 'Only commits before or on this date will be returned',
documentation: { example: '2021-09-20T11:50:22.001' }
optional :path,
type: String,
desc: 'The file path',
documentation: { example: 'README.md' }
2018-11-08 19:23:39 +05:30
optional :all, type: Boolean, desc: 'Every commit will be returned'
optional :with_stats, type: Boolean, desc: 'Stats about each commit will be added to the response'
2019-12-21 20:55:43 +05:30
optional :first_parent, type: Boolean, desc: 'Only include the first parent of merges'
2020-03-13 15:44:24 +05:30
optional :order, type: String, desc: 'List commits in order', default: 'default', values: %w[default topo]
2021-09-04 01:27:46 +05:30
optional :trailers, type: Boolean, desc: 'Parse and include Git trailers for every commit', default: false
2017-08-17 22:00:37 +05:30
use :pagination
2016-11-03 12:29:30 +05:30
end
2021-12-11 22:18:48 +05:30
get ':id/repository/commits', urgency: :low do
2022-05-07 20:08:51 +05:30
not_found! 'Repository' unless user_project.repository_exists?
2018-11-08 19:23:39 +05:30
path = params[:path]
2017-08-17 22:00:37 +05:30
before = params[:until]
2018-11-08 19:23:39 +05:30
after = params[:since]
2021-10-27 15:23:28 +05:30
ref = params[:ref_name].presence || user_project.default_branch unless params[:all]
2017-08-17 22:00:37 +05:30
offset = (params[:page] - 1) * params[:per_page]
2018-11-08 19:23:39 +05:30
all = params[:all]
with_stats = params[:with_stats]
2019-12-21 20:55:43 +05:30
first_parent = params[:first_parent]
2020-03-13 15:44:24 +05:30
order = params[:order]
2016-11-03 12:29:30 +05:30
commits = user_project.repository.commits(ref,
2017-08-17 22:00:37 +05:30
path: path,
2016-11-03 12:29:30 +05:30
limit: params[:per_page],
offset: offset,
2017-08-17 22:00:37 +05:30
before: before,
2018-03-27 19:54:05 +05:30
after: after,
2019-12-21 20:55:43 +05:30
all: all,
2020-03-13 15:44:24 +05:30
first_parent: first_parent,
2021-09-04 01:27:46 +05:30
order: order,
trailers: params[:trailers])
2017-08-17 22:00:37 +05:30
2021-01-03 14:25:43 +05:30
serializer = with_stats ? Entities::CommitWithStats : Entities::Commit
2014-09-02 18:07:02 +05:30
2021-01-03 14:25:43 +05:30
# This tells kaminari that there is 1 more commit after the one we've
# loaded, meaning there will be a next page, if the currently loaded set
# of commits is equal to the requested page size.
commit_count = offset + commits.size + 1
2017-08-17 22:00:37 +05:30
paginated_commits = Kaminari.paginate_array(commits, total_count: commit_count)
2021-01-03 14:25:43 +05:30
present paginate(paginated_commits, exclude_total_headers: true), with: serializer
2014-09-02 18:07:02 +05:30
end
2016-11-03 12:29:30 +05:30
desc 'Commit multiple file changes as one commit' do
2023-01-13 00:05:48 +05:30
success code: 200, model: Entities::CommitDetail
tags %w[commits]
failure [
{ code: 400, message: 'Bad request' },
{ code: 404, message: 'Not found' }
]
2016-11-03 12:29:30 +05:30
detail 'This feature was introduced in GitLab 8.13'
end
params do
2023-01-13 00:05:48 +05:30
requires :branch,
type: String,
desc: 'Name of the branch to commit into. To create a new branch, also provide either `start_branch` or `start_sha`, and optionally `start_project`.',
allow_blank: false,
documentation: { example: 'master' }
requires :commit_message,
type: String,
desc: 'Commit message',
documentation: { example: 'initial commit' }
requires :actions,
type: Array,
desc: 'Actions to perform in commit' do
requires :action,
type: String,
desc: 'The action to perform, `create`, `delete`, `move`, `update`, `chmod`', values: %w[create update move delete chmod].freeze,
allow_blank: false
requires :file_path,
type: String,
desc: 'Full path to the file.',
documentation: { example: 'lib/class.rb' }
2018-12-05 23:21:45 +05:30
given action: ->(action) { action == 'move' } do
2023-01-13 00:05:48 +05:30
requires :previous_path,
type: String,
desc: 'Original full path to the file being moved.',
documentation: { example: 'lib/class.rb' }
2018-12-05 23:21:45 +05:30
end
given action: ->(action) { %w[create move].include? action } do
2023-01-13 00:05:48 +05:30
optional :content,
type: String,
desc: 'File content',
documentation: { example: 'Some file content' }
2018-12-05 23:21:45 +05:30
end
given action: ->(action) { action == 'update' } do
2023-01-13 00:05:48 +05:30
requires :content,
type: String,
desc: 'File content',
documentation: { example: 'Some file content' }
2018-12-05 23:21:45 +05:30
end
optional :encoding, type: String, desc: '`text` or `base64`', default: 'text', values: %w[text base64]
given action: ->(action) { %w[update move delete].include? action } do
2023-01-13 00:05:48 +05:30
optional :last_commit_id,
type: String,
desc: 'Last known file commit id',
documentation: { example: '2695effb5807a22ff3d138d593fd856244e155e7' }
2018-12-05 23:21:45 +05:30
end
given action: ->(action) { action == 'chmod' } do
requires :execute_filemode, type: Boolean, desc: 'When `true/false` enables/disables the execute flag on the file.'
end
end
2019-10-12 21:52:04 +05:30
2023-01-13 00:05:48 +05:30
optional :start_branch,
type: String,
desc: 'Name of the branch to start the new branch from',
documentation: { example: 'staging' }
optional :start_sha,
type: String,
desc: 'SHA of the commit to start the new branch from',
documentation: { example: '2695effb5807a22ff3d138d593fd856244e155e7' }
2019-10-12 21:52:04 +05:30
mutually_exclusive :start_branch, :start_sha
2023-01-13 00:05:48 +05:30
optional :start_project,
types: [Integer, String],
desc: 'The ID or path of the project to start the new branch from',
documentation: { example: 1 }
optional :author_email,
type: String,
desc: 'Author email for commit',
documentation: { example: 'janedoe@example.com' }
optional :author_name,
type: String,
desc: 'Author name for commit',
documentation: { example: 'Jane Doe' }
2018-12-05 23:21:45 +05:30
optional :stats, type: Boolean, default: true, desc: 'Include commit stats'
2019-10-12 21:52:04 +05:30
optional :force, type: Boolean, default: false, desc: 'When `true` overwrites the target branch with a new commit based on the `start_branch` or `start_sha`'
2016-11-03 12:29:30 +05:30
end
2017-09-10 17:25:29 +05:30
post ':id/repository/commits' do
2019-09-04 21:01:54 +05:30
if params[:start_project]
start_project = find_project!(params[:start_project])
unless user_project.forked_from?(start_project)
forbidden!("Project is not included in the fork network for #{start_project.full_name}")
end
end
2018-11-18 11:00:15 +05:30
authorize_push_to_branch!(params[:branch])
2016-11-03 12:29:30 +05:30
2017-09-10 17:25:29 +05:30
attrs = declared_params
attrs[:branch_name] = attrs.delete(:branch)
2019-10-12 21:52:04 +05:30
attrs[:start_branch] ||= attrs[:branch_name] unless attrs[:start_sha]
2019-09-04 21:01:54 +05:30
attrs[:start_project] = start_project if start_project
2016-11-03 12:29:30 +05:30
result = ::Files::MultiService.new(user_project, current_user, attrs).execute
if result[:status] == :success
2017-09-10 17:25:29 +05:30
commit_detail = user_project.repository.commit(result[:result])
2018-12-05 23:21:45 +05:30
2020-11-24 15:15:51 +05:30
if find_user_from_warden
Gitlab::UsageDataCounters::WebIdeCounter.increment_commits_count
2022-08-13 15:12:31 +05:30
Gitlab::UsageDataCounters::EditorUniqueCounter.track_web_ide_edit_action(author: current_user, project: user_project)
2020-11-24 15:15:51 +05:30
end
2018-12-05 23:21:45 +05:30
2022-09-01 20:07:04 +05:30
present commit_detail, with: Entities::CommitDetail, include_stats: params[:stats], current_user: current_user
2016-11-03 12:29:30 +05:30
else
render_api_error!(result[:message], 400)
end
end
desc 'Get a specific commit of a project' do
2023-01-13 00:05:48 +05:30
success code: 200, model: Entities::CommitDetail
tags %w[commits]
failure [
{ code: 404, message: 'Not found' }
]
2016-11-03 12:29:30 +05:30
end
params do
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
2018-03-17 18:26:18 +05:30
optional :stats, type: Boolean, default: true, desc: 'Include commit stats'
2016-11-03 12:29:30 +05:30
end
2018-03-17 18:26:18 +05:30
get ':id/repository/commits/:sha', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
2016-11-03 12:29:30 +05:30
commit = user_project.commit(params[:sha])
2017-09-10 17:25:29 +05:30
not_found! 'Commit' unless commit
2016-11-03 12:29:30 +05:30
2022-09-01 20:07:04 +05:30
present commit, with: Entities::CommitDetail, include_stats: params[:stats], current_user: current_user
2014-09-02 18:07:02 +05:30
end
2016-11-03 12:29:30 +05:30
desc 'Get the diff for a specific commit of a project' do
2023-01-13 00:05:48 +05:30
success code: 200, model: Entities::Diff
tags %w[commits]
is_array true
failure [
{ code: 404, message: 'Not found' }
]
2016-11-03 12:29:30 +05:30
end
params do
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
2018-03-27 19:54:05 +05:30
use :pagination
2016-11-03 12:29:30 +05:30
end
2021-12-11 22:18:48 +05:30
get ':id/repository/commits/:sha/diff', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS, urgency: :low do
2016-11-03 12:29:30 +05:30
commit = user_project.commit(params[:sha])
2017-09-10 17:25:29 +05:30
not_found! 'Commit' unless commit
2016-11-03 12:29:30 +05:30
2019-12-26 22:10:19 +05:30
raw_diffs = ::Kaminari.paginate_array(commit.diffs(expanded: true).diffs.to_a)
2018-03-27 19:54:05 +05:30
present paginate(raw_diffs), with: Entities::Diff
2014-09-02 18:07:02 +05:30
end
2015-04-26 12:48:37 +05:30
2016-11-03 12:29:30 +05:30
desc "Get a commit's comments" do
2023-01-13 00:05:48 +05:30
success code: 200, model: Entities::CommitNote
tags %w[commits]
is_array true
failure [
{ code: 404, message: 'Not found' }
]
2016-11-03 12:29:30 +05:30
end
params do
2017-08-17 22:00:37 +05:30
use :pagination
2016-11-03 12:29:30 +05:30
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
end
2018-03-17 18:26:18 +05:30
get ':id/repository/commits/:sha/comments', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
2016-11-03 12:29:30 +05:30
commit = user_project.commit(params[:sha])
2015-04-26 12:48:37 +05:30
not_found! 'Commit' unless commit
2021-04-29 21:17:54 +05:30
notes = commit.notes.with_api_entity_associations.fresh
2016-11-03 12:29:30 +05:30
2015-04-26 12:48:37 +05:30
present paginate(notes), with: Entities::CommitNote
end
2017-08-17 22:00:37 +05:30
desc 'Cherry pick commit into a branch' do
detail 'This feature was introduced in GitLab 8.15'
2023-01-13 00:05:48 +05:30
success code: 200, model: Entities::Commit
tags %w[commits]
failure [
{ code: 400, message: 'Bad request' },
{ code: 404, message: 'Not found' }
]
2017-08-17 22:00:37 +05:30
end
params do
2017-09-10 17:25:29 +05:30
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag to be cherry picked'
2023-01-13 00:05:48 +05:30
requires :branch,
type: String,
desc: 'The name of the branch',
allow_blank: false,
documentation: { example: 'master' }
2020-10-24 23:57:45 +05:30
optional :dry_run, type: Boolean, default: false, desc: "Does not commit any changes"
2023-01-13 00:05:48 +05:30
optional :message,
type: String,
desc: 'A custom commit message to use for the picked commit',
documentation: { example: 'Initial commit' }
2017-08-17 22:00:37 +05:30
end
2018-03-17 18:26:18 +05:30
post ':id/repository/commits/:sha/cherry_pick', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
2018-11-18 11:00:15 +05:30
authorize_push_to_branch!(params[:branch])
2017-08-17 22:00:37 +05:30
commit = user_project.commit(params[:sha])
not_found!('Commit') unless commit
2018-11-20 20:47:30 +05:30
find_branch!(params[:branch])
2017-08-17 22:00:37 +05:30
commit_params = {
commit: commit,
start_branch: params[:branch],
2020-10-24 23:57:45 +05:30
branch_name: params[:branch],
2021-09-04 01:27:46 +05:30
dry_run: params[:dry_run],
message: params[:message]
2017-08-17 22:00:37 +05:30
}
2019-02-15 15:39:39 +05:30
result = ::Commits::CherryPickService
.new(user_project, current_user, commit_params)
.execute
2017-08-17 22:00:37 +05:30
if result[:status] == :success
2020-10-24 23:57:45 +05:30
if params[:dry_run]
present dry_run: :success
status :ok
else
present user_project.repository.commit(result[:result]),
with: Entities::Commit
end
2017-08-17 22:00:37 +05:30
else
2020-10-24 23:57:45 +05:30
response = result.slice(:message, :error_code)
response[:dry_run] = :error if params[:dry_run]
error!(response, 400, header)
2017-08-17 22:00:37 +05:30
end
end
2018-12-13 13:39:08 +05:30
desc 'Revert a commit in a branch' do
detail 'This feature was introduced in GitLab 11.5'
2023-01-13 00:05:48 +05:30
success code: 200, model: Entities::Commit
tags %w[commits]
failure [
{ code: 400, message: 'Bad request' },
{ code: 404, message: 'Not found' }
]
2018-12-13 13:39:08 +05:30
end
params do
requires :sha, type: String, desc: 'Commit SHA to revert'
2023-01-13 00:05:48 +05:30
requires :branch,
type: String,
desc: 'Target branch name',
allow_blank: false,
documentation: { example: 'master' }
2020-10-24 23:57:45 +05:30
optional :dry_run, type: Boolean, default: false, desc: "Does not commit any changes"
2018-12-13 13:39:08 +05:30
end
post ':id/repository/commits/:sha/revert', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
authorize_push_to_branch!(params[:branch])
commit = user_project.commit(params[:sha])
not_found!('Commit') unless commit
find_branch!(params[:branch])
commit_params = {
commit: commit,
start_branch: params[:branch],
2020-10-24 23:57:45 +05:30
branch_name: params[:branch],
dry_run: params[:dry_run]
2018-12-13 13:39:08 +05:30
}
result = ::Commits::RevertService
.new(user_project, current_user, commit_params)
.execute
if result[:status] == :success
2020-10-24 23:57:45 +05:30
if params[:dry_run]
present dry_run: :success
status :ok
else
present user_project.repository.commit(result[:result]),
with: Entities::Commit
end
2018-12-13 13:39:08 +05:30
else
2020-10-24 23:57:45 +05:30
response = result.slice(:message, :error_code)
response[:dry_run] = :error if params[:dry_run]
error!(response, 400, header)
2018-12-13 13:39:08 +05:30
end
end
2018-03-27 19:54:05 +05:30
desc 'Get all references a commit is pushed to' do
detail 'This feature was introduced in GitLab 10.6'
2023-01-13 00:05:48 +05:30
success code: 200, model: Entities::BasicRef
tags %w[commits]
is_array true
failure [
{ code: 404, message: 'Not found' }
]
2018-03-27 19:54:05 +05:30
end
params do
requires :sha, type: String, desc: 'A commit sha'
optional :type, type: String, values: %w[branch tag all], default: 'all', desc: 'Scope'
use :pagination
end
2021-12-11 22:18:48 +05:30
get ':id/repository/commits/:sha/refs', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS, urgency: :low do
2018-03-27 19:54:05 +05:30
commit = user_project.commit(params[:sha])
not_found!('Commit') unless commit
refs = []
2022-08-27 11:52:29 +05:30
refs.concat(user_project.repository.branch_names_contains(commit.id).map { |name| { type: 'branch', name: name } }) unless params[:type] == 'tag'
refs.concat(user_project.repository.tag_names_contains(commit.id).map { |name| { type: 'tag', name: name } }) unless params[:type] == 'branch'
2018-03-27 19:54:05 +05:30
refs = Kaminari.paginate_array(refs)
present paginate(refs), with: Entities::BasicRef
end
2016-11-03 12:29:30 +05:30
desc 'Post comment to commit' do
2023-01-13 00:05:48 +05:30
success code: 200, model: Entities::CommitNote
tags %w[commits]
failure [
{ code: 400, message: 'Bad request' },
{ code: 404, message: 'Not found' }
]
2016-11-03 12:29:30 +05:30
end
params do
2017-09-10 17:25:29 +05:30
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag on which to post a comment'
2023-01-13 00:05:48 +05:30
requires :note,
type: String,
desc: 'The text of the comment',
documentation: { example: 'Nice code!' }
optional :path,
type: String,
desc: 'The file path',
documentation: { example: 'doc/update/5.4-to-6.0.md' }
2016-11-03 12:29:30 +05:30
given :path do
2023-01-13 00:05:48 +05:30
requires :line,
type: Integer,
desc: 'The line number',
documentation: { example: 11 }
2018-03-27 19:54:05 +05:30
requires :line_type, type: String, values: %w[new old], default: 'new', desc: 'The type of the line'
2016-11-03 12:29:30 +05:30
end
end
2018-03-17 18:26:18 +05:30
post ':id/repository/commits/:sha/comments', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
2016-11-03 12:29:30 +05:30
commit = user_project.commit(params[:sha])
2015-04-26 12:48:37 +05:30
not_found! 'Commit' unless commit
2016-11-03 12:29:30 +05:30
2015-04-26 12:48:37 +05:30
opts = {
note: params[:note],
noteable_type: 'Commit',
commit_id: commit.id
}
2016-11-03 12:29:30 +05:30
if params[:path]
2017-09-10 17:25:29 +05:30
commit.raw_diffs(limits: false).each do |diff|
2015-04-26 12:48:37 +05:30
next unless diff.new_path == params[:path]
2018-03-17 18:26:18 +05:30
2016-06-02 11:05:42 +05:30
lines = Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
2015-04-26 12:48:37 +05:30
lines.each do |line|
2021-11-11 11:23:49 +05:30
next unless line.line == params[:line] && line.type == params[:line_type]
2018-03-17 18:26:18 +05:30
break opts[:line_code] = Gitlab::Git.diff_line_code(diff.new_path, line.new_pos, line.old_pos)
2015-04-26 12:48:37 +05:30
end
break if opts[:line_code]
end
2016-06-02 11:05:42 +05:30
opts[:type] = LegacyDiffNote.name if opts[:line_code]
2015-04-26 12:48:37 +05:30
end
note = ::Notes::CreateService.new(user_project, current_user, opts).execute
if note.save
present note, with: Entities::CommitNote
else
render_api_error!("Failed to save note #{note.errors.messages}", 400)
end
end
2018-05-09 12:01:36 +05:30
desc 'Get Merge Requests associated with a commit' do
2023-01-13 00:05:48 +05:30
success code: 200, model: Entities::MergeRequestBasic
tags %w[commits]
is_array true
failure [
{ code: 404, message: 'Not found' }
]
2018-05-09 12:01:36 +05:30
end
params do
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag on which to find Merge Requests'
use :pagination
end
2021-12-11 22:18:48 +05:30
get ':id/repository/commits/:sha/merge_requests', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS, urgency: :low do
2019-03-13 22:55:13 +05:30
authorize! :read_merge_request, user_project
2018-05-09 12:01:36 +05:30
commit = user_project.commit(params[:sha])
not_found! 'Commit' unless commit
2019-03-13 22:55:13 +05:30
commit_merge_requests = MergeRequestsFinder.new(
current_user,
project_id: user_project.id,
commit_sha: commit.sha
2021-04-29 21:17:54 +05:30
).execute.with_api_entity_associations
2019-03-13 22:55:13 +05:30
present paginate(commit_merge_requests), with: Entities::MergeRequestBasic
2018-05-09 12:01:36 +05:30
end
2019-07-07 11:18:12 +05:30
2020-04-22 19:07:51 +05:30
desc "Get a commit's signature" do
2023-01-13 00:05:48 +05:30
success code: 200, model: Entities::CommitSignature
tags %w[commits]
failure [
{ code: 404, message: 'Not found' }
]
2019-07-07 11:18:12 +05:30
end
params do
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
end
get ':id/repository/commits/:sha/signature', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
commit = user_project.commit(params[:sha])
not_found! 'Commit' unless commit
2020-04-22 19:07:51 +05:30
not_found! 'Signature' unless commit.has_signature?
2019-07-07 11:18:12 +05:30
2020-04-22 19:07:51 +05:30
present commit, with: Entities::CommitSignature
2019-07-07 11:18:12 +05:30
end
2014-09-02 18:07:02 +05:30
end
end
end