commits.rb 10.2 KB
Newer Older
1 2 3 4
require 'mime/types'

module API
  class Commits < Grape::API
5 6
    include PaginationParams

7 8
    before { authorize! :download_code, user_project }

9 10 11 12 13 14 15 16 17 18 19 20
    helpers do
      def user_access
        @user_access ||= Gitlab::UserAccess.new(current_user, project: user_project)
      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

Z.J. van de Weg's avatar
Z.J. van de Weg committed
21 22 23
    params do
      requires :id, type: String, desc: 'The ID of a project'
    end
24
    resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
Z.J. van de Weg's avatar
Z.J. van de Weg committed
25
      desc 'Get a project repository commits' do
26
        success Entities::Commit
Z.J. van de Weg's avatar
Z.J. van de Weg committed
27 28 29
      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'
30 31 32 33 34
        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'
35
        use :pagination
Z.J. van de Weg's avatar
Z.J. van de Weg committed
36
      end
37
      get ':id/repository/commits' do
38
        path = params[:path]
39
        before = params[:until]
40 41
        after = params[:since]
        ref = params[:ref_name] || user_project.try(:default_branch) || 'master' unless params[:all]
42
        offset = (params[:page] - 1) * params[:per_page]
43 44
        all = params[:all]
        with_stats = params[:with_stats]
Z.J. van de Weg's avatar
Z.J. van de Weg committed
45 46

        commits = user_project.repository.commits(ref,
47
                                                  path: path,
Z.J. van de Weg's avatar
Z.J. van de Weg committed
48 49
                                                  limit: params[:per_page],
                                                  offset: offset,
50
                                                  before: before,
51 52
                                                  after: after,
                                                  all: all)
53

54
        commit_count =
Tiago Botelho's avatar
Tiago Botelho committed
55
          if all || path || before || after
56
            user_project.repository.count_commits(ref: ref, path: path, before: before, after: after, all: all)
57 58 59 60 61 62
          else
            # Cacheable commit count.
            user_project.repository.commit_count_for_ref(ref)
          end

        paginated_commits = Kaminari.paginate_array(commits, total_count: commit_count)
63

64 65 66
        serializer = with_stats ? Entities::CommitWithStats : Entities::Commit

        present paginate(paginated_commits), with: serializer
67 68
      end

Marc Siegfriedt's avatar
Marc Siegfriedt committed
69
      desc 'Commit multiple file changes as one commit' do
70
        success Entities::CommitDetail
Marc Siegfriedt's avatar
Marc Siegfriedt committed
71 72 73
        detail 'This feature was introduced in GitLab 8.13'
      end
      params do
74
        requires :branch, type: String, desc: 'Name of the branch to commit into. To create a new branch, also provide `start_branch`.'
Marc Siegfriedt's avatar
Marc Siegfriedt committed
75
        requires :commit_message, type: String, desc: 'Commit message'
76
        requires :actions, type: Array[Hash], desc: 'Actions to perform in commit'
77
        optional :start_branch, type: String, desc: 'Name of the branch to start the new commit from'
Marc Siegfriedt's avatar
Marc Siegfriedt committed
78 79 80
        optional :author_email, type: String, desc: 'Author email for commit'
        optional :author_name, type: String, desc: 'Author name for commit'
      end
81
      post ':id/repository/commits' do
82
        authorize_push_to_branch!(params[:branch])
Marc Siegfriedt's avatar
Marc Siegfriedt committed
83

Douwe Maan's avatar
Douwe Maan committed
84 85
        attrs = declared_params
        attrs[:branch_name] = attrs.delete(:branch)
86
        attrs[:start_branch] ||= attrs[:branch_name]
87

Marc Siegfriedt's avatar
Marc Siegfriedt committed
88 89 90
        result = ::Files::MultiService.new(user_project, current_user, attrs).execute

        if result[:status] == :success
91
          commit_detail = user_project.repository.commit(result[:result])
92
          present commit_detail, with: Entities::CommitDetail
Marc Siegfriedt's avatar
Marc Siegfriedt committed
93 94 95 96 97
        else
          render_api_error!(result[:message], 400)
        end
      end

Z.J. van de Weg's avatar
Z.J. van de Weg committed
98
      desc 'Get a specific commit of a project' do
99
        success Entities::CommitDetail
100
        failure [[404, 'Commit Not Found']]
Z.J. van de Weg's avatar
Z.J. van de Weg committed
101 102 103
      end
      params do
        requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
104
        optional :stats, type: Boolean, default: true, desc: 'Include commit stats'
Z.J. van de Weg's avatar
Z.J. van de Weg committed
105
      end
106
      get ':id/repository/commits/:sha', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
Z.J. van de Weg's avatar
Z.J. van de Weg committed
107 108
        commit = user_project.commit(params[:sha])

109
        not_found! 'Commit' unless commit
Z.J. van de Weg's avatar
Z.J. van de Weg committed
110

111
        present commit, with: Entities::CommitDetail, stats: params[:stats]
112 113
      end

Z.J. van de Weg's avatar
Z.J. van de Weg committed
114
      desc 'Get the diff for a specific commit of a project' do
115
        failure [[404, 'Commit Not Found']]
Z.J. van de Weg's avatar
Z.J. van de Weg committed
116 117 118
      end
      params do
        requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
119
        use :pagination
Z.J. van de Weg's avatar
Z.J. van de Weg committed
120
      end
121
      get ':id/repository/commits/:sha/diff', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
Z.J. van de Weg's avatar
Z.J. van de Weg committed
122 123
        commit = user_project.commit(params[:sha])

124
        not_found! 'Commit' unless commit
Z.J. van de Weg's avatar
Z.J. van de Weg committed
125

126 127 128
        raw_diffs = ::Kaminari.paginate_array(commit.raw_diffs.to_a)

        present paginate(raw_diffs), with: Entities::Diff
129
      end
130

Z.J. van de Weg's avatar
Z.J. van de Weg committed
131 132
      desc "Get a commit's comments" do
        success Entities::CommitNote
133
        failure [[404, 'Commit Not Found']]
Z.J. van de Weg's avatar
Z.J. van de Weg committed
134 135
      end
      params do
136
        use :pagination
Z.J. van de Weg's avatar
Z.J. van de Weg committed
137 138
        requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
      end
139
      get ':id/repository/commits/:sha/comments', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
Z.J. van de Weg's avatar
Z.J. van de Weg committed
140 141
        commit = user_project.commit(params[:sha])

142
        not_found! 'Commit' unless commit
143
        notes = commit.notes.order(:created_at)
Z.J. van de Weg's avatar
Z.J. van de Weg committed
144

145 146 147
        present paginate(notes), with: Entities::CommitNote
      end

148 149
      desc 'Cherry pick commit into a branch' do
        detail 'This feature was introduced in GitLab 8.15'
150
        success Entities::Commit
151 152
      end
      params do
153
        requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag to be cherry picked'
154 155
        requires :branch, type: String, desc: 'The name of the branch'
      end
156
      post ':id/repository/commits/:sha/cherry_pick', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
157
        authorize_push_to_branch!(params[:branch])
158 159 160 161 162 163 164 165 166

        commit = user_project.commit(params[:sha])
        not_found!('Commit') unless commit

        branch = user_project.repository.find_branch(params[:branch])
        not_found!('Branch') unless branch

        commit_params = {
          commit: commit,
167
          start_branch: params[:branch],
Douwe Maan's avatar
Douwe Maan committed
168
          branch_name: params[:branch]
169 170 171 172 173 174
        }

        result = ::Commits::CherryPickService.new(user_project, current_user, commit_params).execute

        if result[:status] == :success
          branch = user_project.repository.find_branch(params[:branch])
175
          present user_project.repository.commit(branch.dereferenced_target), with: Entities::Commit
176 177 178 179 180
        else
          render_api_error!(result[:message], 400)
        end
      end

Robert Schilling's avatar
Robert Schilling committed
181
      desc 'Get all references a commit is pushed to' do
182
        detail 'This feature was introduced in GitLab 10.6'
Robert Schilling's avatar
Robert Schilling committed
183
        success Entities::BasicRef
184 185 186
      end
      params do
        requires :sha, type: String, desc: 'A commit sha'
187 188
        optional :type, type: String, values: %w[branch tag all], default: 'all', desc: 'Scope'
        use :pagination
189 190 191 192 193
      end
      get ':id/repository/commits/:sha/refs', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
        commit = user_project.commit(params[:sha])
        not_found!('Commit') unless commit

194 195 196 197
        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)
198

199
        present paginate(refs), with: Entities::BasicRef
200 201
      end

Z.J. van de Weg's avatar
Z.J. van de Weg committed
202 203 204 205
      desc 'Post comment to commit' do
        success Entities::CommitNote
      end
      params do
206
        requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag on which to post a comment'
Z.J. van de Weg's avatar
Z.J. van de Weg committed
207 208 209 210
        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'
Robert Schilling's avatar
Robert Schilling committed
211
          requires :line_type, type: String, values: %w[new old], default: 'new', desc: 'The type of the line'
Z.J. van de Weg's avatar
Z.J. van de Weg committed
212 213
        end
      end
214
      post ':id/repository/commits/:sha/comments', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
Z.J. van de Weg's avatar
Z.J. van de Weg committed
215
        commit = user_project.commit(params[:sha])
216
        not_found! 'Commit' unless commit
Z.J. van de Weg's avatar
Z.J. van de Weg committed
217

218 219 220 221 222 223
        opts = {
          note: params[:note],
          noteable_type: 'Commit',
          commit_id: commit.id
        }

Z.J. van de Weg's avatar
Z.J. van de Weg committed
224
        if params[:path]
Douwe Maan's avatar
Douwe Maan committed
225
          commit.raw_diffs(limits: false).each do |diff|
226
            next unless diff.new_path == params[:path]
227

228
            lines = Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
229 230

            lines.each do |line|
Z.J. van de Weg's avatar
Z.J. van de Weg committed
231
              next unless line.new_pos == params[:line] && line.type == params[:line_type]
232

233
              break opts[:line_code] = Gitlab::Git.diff_line_code(diff.new_path, line.new_pos, line.old_pos)
234 235 236 237
            end

            break if opts[:line_code]
          end
238 239

          opts[:type] = LegacyDiffNote.name if opts[:line_code]
240 241 242 243 244 245 246
        end

        note = ::Notes::CreateService.new(user_project, current_user, opts).execute

        if note.save
          present note, with: Entities::CommitNote
        else
247
          render_api_error!("Failed to save note #{note.errors.messages}", 400)
248 249
        end
      end
250 251 252 253 254 255 256 257 258 259 260 261 262 263

      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
        commit = user_project.commit(params[:sha])
        not_found! 'Commit' unless commit

        present paginate(commit.merge_requests), with: Entities::MergeRequestBasic
      end
264 265 266
    end
  end
end