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!
authorize! :download_code , user_project
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
requires :id , type : String , desc : 'The ID of a project'
end
2019-02-15 15:39:39 +05:30
resource :projects , requirements : API :: NAMESPACE_OR_PROJECT_REQUIREMENTS do
2016-11-03 12:29:30 +05:30
desc 'Get a project repository commits' do
2018-03-17 18:26:18 +05:30
success Entities :: Commit
2016-11-03 12:29:30 +05:30
end
params do
optional :ref_name , type : String , desc : 'The name of a repository branch or tag, if not given the default branch is used'
2018-11-08 19:23:39 +05:30
optional :since , type : DateTime , desc : 'Only commits after or on this date will be returned'
optional :until , type : DateTime , desc : 'Only commits before or on this date will be returned'
optional :path , type : String , desc : 'The file path'
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 ]
2017-08-17 22:00:37 +05:30
use :pagination
2016-11-03 12:29:30 +05:30
end
2017-09-10 17:25:29 +05:30
get ':id/repository/commits' do
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 ]
2019-10-12 21:52:04 +05:30
ref = params [ :ref_name ] . presence || user_project . try ( :default_branch ) || 'master' 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 ,
order : order )
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
2018-03-17 18:26:18 +05:30
success Entities :: CommitDetail
2016-11-03 12:29:30 +05:30
detail 'This feature was introduced in GitLab 8.13'
end
params do
2019-10-12 21:52:04 +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
2016-11-03 12:29:30 +05:30
requires :commit_message , type : String , desc : 'Commit message'
2018-12-05 23:21:45 +05:30
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
requires :file_path , type : String , desc : 'Full path to the file. Ex. `lib/class.rb`'
given action : - > ( action ) { action == 'move' } do
requires :previous_path , type : String , desc : 'Original full path to the file being moved. Ex. `lib/class1.rb`'
end
given action : - > ( action ) { %w[ create move ] . include? action } do
optional :content , type : String , desc : 'File content'
end
given action : - > ( action ) { action == 'update' } do
requires :content , type : String , desc : 'File content'
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
optional :last_commit_id , type : String , desc : 'Last known file commit id'
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
optional :start_branch , type : String , desc : 'Name of the branch to start the new branch from'
optional :start_sha , type : String , desc : 'SHA of the commit to start the new branch from'
mutually_exclusive :start_branch , :start_sha
optional :start_project , types : [ Integer , String ] , desc : 'The ID or path of the project to start the new branch from'
2016-11-03 12:29:30 +05:30
optional :author_email , type : String , desc : 'Author email for commit'
optional :author_name , type : String , desc : 'Author name for commit'
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
Gitlab :: UsageDataCounters :: EditorUniqueCounter . track_web_ide_edit_action ( author : current_user )
end
2018-12-05 23:21:45 +05:30
present commit_detail , with : Entities :: CommitDetail , stats : params [ :stats ]
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
2018-03-17 18:26:18 +05:30
success Entities :: CommitDetail
2017-09-10 17:25:29 +05:30
failure [ [ 404 , 'Commit 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
2020-02-01 01:16:34 +05:30
present commit , with : Entities :: CommitDetail , 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
2017-09-10 17:25:29 +05:30
failure [ [ 404 , 'Commit 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
2018-03-17 18:26:18 +05:30
get ':id/repository/commits/:sha/diff' , 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
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
success Entities :: CommitNote
2017-09-10 17:25:29 +05:30
failure [ [ 404 , 'Commit 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'
2018-03-17 18:26:18 +05:30
success Entities :: Commit
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'
2018-12-05 23:21:45 +05:30
requires :branch , type : String , desc : 'The name of the branch' , allow_blank : false
2020-10-24 23:57:45 +05:30
optional :dry_run , type : Boolean , default : false , desc : " Does not commit any changes "
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 ] ,
dry_run : params [ :dry_run ]
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'
success Entities :: Commit
end
params do
requires :sha , type : String , desc : 'Commit SHA to revert'
requires :branch , type : String , desc : 'Target branch name' , allow_blank : false
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'
success Entities :: BasicRef
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
get ':id/repository/commits/:sha/refs' , requirements : API :: COMMIT_ENDPOINT_REQUIREMENTS do
commit = user_project . commit ( params [ :sha ] )
not_found! ( 'Commit' ) unless commit
refs = [ ]
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'
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
success Entities :: CommitNote
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'
2016-11-03 12:29:30 +05:30
requires :note , type : String , desc : 'The text of the comment'
optional :path , type : String , desc : 'The file path'
given :path do
requires :line , type : Integer , desc : 'The line number'
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 |
2016-11-03 12:29:30 +05:30
next unless line . new_pos == 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
success Entities :: MergeRequestBasic
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
get ':id/repository/commits/:sha/merge_requests' , requirements : API :: COMMIT_ENDPOINT_REQUIREMENTS 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
2019-07-07 11:18:12 +05:30
success Entities :: CommitSignature
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