merge_requests_controller.rb 12.5 KB
Newer Older
1 2
# frozen_string_literal: true

D
Douwe Maan 已提交
3
class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationController
4
  include ToggleSubscriptionAction
5
  include IssuableActions
D
Douwe Maan 已提交
6
  include RendersNotes
7
  include RendersCommits
8
  include RendersAssignees
9
  include ToggleAwardEmoji
10
  include IssuableCollections
11
  include RecordUserLastActivity
12
  include SourcegraphDecorator
J
Jacob Vosmaer 已提交
13

D
Douwe Maan 已提交
14
  skip_before_action :merge_request, only: [:index, :bulk_update]
15
  before_action :whitelist_query_limiting, only: [:assign_related_issues, :update]
16
  before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort]
17
  before_action :authorize_read_actual_head_pipeline!, only: [:test_reports, :exposed_artifacts, :coverage_reports, :terraform_reports]
J
Jarka Kadlecova 已提交
18
  before_action :set_issuables_index, only: [:index]
19
  before_action :authenticate_user!, only: [:assign_related_issues]
20
  before_action :check_user_can_push_to_source_branch!, only: [:rebase]
21
  before_action only: [:show] do
22
    push_frontend_feature_flag(:diffs_batch_load, @project, default_enabled: true)
23
    push_frontend_feature_flag(:deploy_from_footer, @project, default_enabled: true)
24
    push_frontend_feature_flag(:single_mr_diff_view, @project, default_enabled: true)
25
    push_frontend_feature_flag(:suggest_pipeline) if experiment_enabled?(:suggest_pipeline)
26
    push_frontend_feature_flag(:code_navigation, @project)
27
    push_frontend_feature_flag(:widget_visibility_polling, @project, default_enabled: true)
28
    push_frontend_feature_flag(:merge_ref_head_comments, @project)
29
    push_frontend_feature_flag(:accessibility_merge_request_widget, @project)
30
  end
31

32 33 34 35
  before_action do
    push_frontend_feature_flag(:vue_issuable_sidebar, @project.group)
  end

36
  around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :discussions]
37

D
Dmitriy Zaporozhets 已提交
38
  def index
J
Jarka Kadlecova 已提交
39
    @merge_requests = @issuables
C
Clement Ho 已提交
40

41 42 43 44
    respond_to do |format|
      format.html
      format.json do
        render json: {
45
          html: view_to_html_string("projects/merge_requests/_merge_requests")
46 47 48
        }
      end
    end
D
Dmitriy Zaporozhets 已提交
49 50 51
  end

  def show
52
    close_merge_request_if_no_source_project
53
    @merge_request.check_mergeability(async: true)
54

55
    respond_to do |format|
F
Fatih Acet 已提交
56
      format.html do
57 58 59
        # use next to appease Rubocop
        next render('invalid') if target_branch_missing?

60 61
        preload_assignees_for_render(@merge_request)

D
Douwe Maan 已提交
62 63 64 65 66
        # Build a note object for comment form
        @note = @project.notes.new(noteable: @merge_request)

        @noteable = @merge_request
        @commits_count = @merge_request.commits_count
67
        @issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar')
68
        @current_user_data = UserSerializer.new(project: @project).represent(current_user, {}, MergeRequestUserEntity).to_json
69
        @show_whitespace_default = current_user.nil? || current_user.show_whitespace_in_diffs
70
        @coverage_path = coverage_reports_project_merge_request_path(@project, @merge_request, format: :json) if @merge_request.has_coverage_reports?
D
Douwe Maan 已提交
71 72

        set_pipeline_variables
73

Z
Zeger-Jan van de Weg 已提交
74
        render
F
Fatih Acet 已提交
75
      end
S
Sean McGivern 已提交
76

D
Douwe Maan 已提交
77
      format.json do
78 79
        Gitlab::PollingInterval.set_header(response, interval: 10_000)

E
Eric Eastwood 已提交
80
        render json: serializer.represent(@merge_request, serializer: params[:serializer])
D
Douwe Maan 已提交
81 82
      end

83
      format.patch do
84
        break render_404 unless @merge_request.diff_refs
D
Douwe Maan 已提交
85 86

        send_git_patch @project.repository, @merge_request.diff_refs
Z
Z.J. van de Weg 已提交
87
      end
D
Douwe Maan 已提交
88

89
      format.diff do
90
        break render_404 unless @merge_request.diff_refs
91

D
Douwe Maan 已提交
92
        send_git_diff @project.repository, @merge_request.diff_refs
93
      end
94
    end
R
randx 已提交
95 96
  end

97
  def commits
D
Douwe Maan 已提交
98 99
    # Get commits from repository
    # or from cache if already merged
100
    @commits =
101 102 103 104
      set_commits_for_rendering(
        @merge_request.recent_commits.with_latest_pipeline(@merge_request.source_branch),
          commits_count: @merge_request.commits_count
      )
105

D
Douwe Maan 已提交
106
    render json: { html: view_to_html_string('projects/merge_requests/_commits') }
107 108
  end

109
  def pipelines
110 111
    set_pipeline_variables
    @pipelines = @pipelines.page(params[:page]).per(30)
112

D
Douwe Maan 已提交
113
    Gitlab::PollingInterval.set_header(response, interval: 10_000)
114

115 116 117
    render json: {
      pipelines: PipelineSerializer
        .new(project: @project, current_user: @current_user)
118
        .with_pagination(request, response)
119 120 121 122 123
        .represent(@pipelines),
      count: {
        all: @pipelines.count
      }
    }
D
Dmitriy Zaporozhets 已提交
124 125
  end

126 127 128 129 130 131 132 133 134
  def context_commits
    return render_404 unless project.context_commits_enabled?

    # Get commits from repository
    # or from cache if already merged
    commits = ContextCommitsFinder.new(project, @merge_request, { search: params[:search], limit: params[:limit], offset: params[:offset] }).execute
    render json: CommitEntity.represent(commits, { type: :full, request: merge_request })
  end

S
Shinya Maeda 已提交
135
  def test_reports
M
Matija Čupić 已提交
136
    reports_response(@merge_request.compare_test_reports)
S
Shinya Maeda 已提交
137 138
  end

139 140 141 142 143 144 145 146
  def coverage_reports
    if @merge_request.has_coverage_reports?
      reports_response(@merge_request.find_coverage_reports)
    else
      head :no_content
    end
  end

147 148 149 150
  def terraform_reports
    reports_response(@merge_request.find_terraform_reports)
  end

151 152 153 154 155 156 157 158
  def exposed_artifacts
    if @merge_request.has_exposed_artifacts?
      reports_response(@merge_request.find_exposed_artifacts)
    else
      head :no_content
    end
  end

D
Douwe Maan 已提交
159
  def edit
D
Douwe Maan 已提交
160
    define_edit_vars
D
Douwe Maan 已提交
161 162
  end

D
Dmitriy Zaporozhets 已提交
163
  def update
D
Douwe Maan 已提交
164
    @merge_request = ::MergeRequests::UpdateService.new(project, current_user, merge_request_params).execute(@merge_request)
165

166 167
    respond_to do |format|
      format.html do
168
        if @merge_request.errors.present?
D
Douwe Maan 已提交
169 170
          define_edit_vars

171
          render :edit
172 173
        else
          redirect_to project_merge_request_path(@merge_request.target_project, @merge_request)
174
        end
175
      end
176 177

      format.json do
178 179 180 181 182
        if merge_request.errors.present?
          render json: @merge_request.errors, status: :bad_request
        else
          render json: serializer.represent(@merge_request, serializer: 'basic')
        end
183
      end
D
Dmitriy Zaporozhets 已提交
184
    end
185
  rescue ActiveRecord::StaleObjectError
D
Douwe Maan 已提交
186 187
    define_edit_vars if request.format.html?

188
    render_conflict_response
D
Dmitriy Zaporozhets 已提交
189 190
  end

191
  def remove_wip
D
Douwe Maan 已提交
192
    @merge_request = ::MergeRequests::UpdateService
F
Fatih Acet 已提交
193 194
      .new(project, current_user, wip_event: 'unwip')
      .execute(@merge_request)
195

196
    render json: serialize_widget(@merge_request)
197 198
  end

F
Fatih Acet 已提交
199 200
  def commit_change_content
    render partial: 'projects/merge_requests/widget/commit_change_content', layout: false
V
Valery Sizov 已提交
201 202
  end

S
Shinya Maeda 已提交
203 204
  def cancel_auto_merge
    unless @merge_request.can_cancel_auto_merge?(current_user)
205 206
      return access_denied!
    end
Z
Zeger-Jan van de Weg 已提交
207

S
Shinya Maeda 已提交
208
    AutoMergeService.new(project, current_user).cancel(@merge_request)
F
Fatih Acet 已提交
209

210
    render json: serialize_widget(@merge_request)
Z
Zeger-Jan van de Weg 已提交
211 212
  end

213
  def merge
214 215 216
    access_check_result = merge_access_check

    return access_check_result if access_check_result
217

F
Fatih Acet 已提交
218
    status = merge!
Z
Zeger-Jan van de Weg 已提交
219

F
Fatih Acet 已提交
220 221
    if @merge_request.merge_error
      render json: { status: status, merge_error: @merge_request.merge_error }
222
    else
F
Fatih Acet 已提交
223
      render json: { status: status }
224
    end
R
randx 已提交
225 226
  end

227
  def assign_related_issues
D
Douwe Maan 已提交
228
    result = ::MergeRequests::AssignIssuesService.new(project, current_user, merge_request: @merge_request).execute
229

D
Douwe Maan 已提交
230 231 232 233 234 235 236
    case result[:count]
    when 0
      flash[:error] = "Failed to assign you issues related to the merge request"
    when 1
      flash[:notice] = "1 issue has been assigned to you"
    else
      flash[:notice] = "#{result[:count]} issues have been assigned to you"
237
    end
D
Douwe Maan 已提交
238 239

    redirect_to(merge_request_path(@merge_request))
240 241
  end

242
  def pipeline_status
243
    render json: PipelineSerializer
F
Fatih Acet 已提交
244
      .new(project: @project, current_user: @current_user)
245
      .represent_status(head_pipeline)
246 247
  end

248
  def ci_environments_status
249 250
    environments =
      if ci_environments_status_on_merge_result?
251
        EnvironmentStatus.for_deployed_merge_request(@merge_request, current_user)
252 253 254
      else
        EnvironmentStatus.for_merge_request(@merge_request, current_user)
      end
255

256
    render json: EnvironmentStatusSerializer.new(current_user: current_user).represent(environments)
257 258
  end

259
  def rebase
260
    @merge_request.rebase_async(current_user.id)
261

262
    head :ok
263 264
  rescue MergeRequest::RebaseLockTimeout => e
    render json: { merge_error: e.message }, status: :conflict
265 266
  end

267
  def discussions
268
    merge_request.discussions_diffs.load_highlight
269 270 271 272

    super
  end

D
Dmitriy Zaporozhets 已提交
273 274
  protected

275
  alias_method :subscribable_resource, :merge_request
276
  alias_method :issuable, :merge_request
277
  alias_method :awardable, :merge_request
D
Dmitriy Zaporozhets 已提交
278

G
George Koltsov 已提交
279
  def sorting_field
280 281 282
    MergeRequest::SORTING_PREFERENCE_FIELD
  end

Z
Zeger-Jan van de Weg 已提交
283
  def merge_params
284 285 286 287
    params.permit(merge_params_attributes)
  end

  def merge_params_attributes
288
    MergeRequest::KNOWN_MERGE_PARAMS
Z
Zeger-Jan van de Weg 已提交
289 290
  end

S
Shinya Maeda 已提交
291 292 293
  def auto_merge_requested?
    # Support params[:merge_when_pipeline_succeeds] during the transition period
    params[:auto_merge_strategy].present? || params[:merge_when_pipeline_succeeds].present?
294
  end
S
Sean McGivern 已提交
295

F
Fatih Acet 已提交
296 297
  private

298 299 300 301 302 303 304
  def head_pipeline
    strong_memoize(:head_pipeline) do
      pipeline = @merge_request.head_pipeline
      pipeline if can?(current_user, :read_pipeline, pipeline)
    end
  end

305 306 307 308
  def ci_environments_status_on_merge_result?
    params[:environment_target] == 'merge_commit'
  end

309 310 311 312
  def target_branch_missing?
    @merge_request.has_no_commits? && !@merge_request.target_branch_exists?
  end

F
Fatih Acet 已提交
313
  def merge!
S
Shinya Maeda 已提交
314
    # Disable the CI check if auto_merge_strategy is specified since we have
F
Fatih Acet 已提交
315
    # to wait until CI completes to know
S
Shinya Maeda 已提交
316
    unless @merge_request.mergeable?(skip_ci_check: auto_merge_requested?)
F
Fatih Acet 已提交
317 318 319
      return :failed
    end

320 321 322 323 324 325
    merge_service = ::MergeRequests::MergeService.new(@project, current_user, merge_params)

    unless merge_service.hooks_validation_pass?(@merge_request)
      return :hook_validation_error
    end

F
Fatih Acet 已提交
326 327
    return :sha_mismatch if params[:sha] != @merge_request.diff_head_sha

328
    @merge_request.update(merge_error: nil, squash: params.fetch(:squash, false))
F
Fatih Acet 已提交
329

S
Shinya Maeda 已提交
330
    if auto_merge_requested?
331 332
      if merge_request.auto_merge_enabled?
        # TODO: We should have a dedicated endpoint for updating merge params.
333
        #       See https://gitlab.com/gitlab-org/gitlab-foss/issues/63130.
334 335 336 337 338 339
        AutoMergeService.new(project, current_user, merge_params).update(merge_request)
      else
        AutoMergeService.new(project, current_user, merge_params)
          .execute(merge_request,
                   params[:auto_merge_strategy] || AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
      end
F
Fatih Acet 已提交
340
    else
341
      @merge_request.merge_async(current_user.id, merge_params)
F
Fatih Acet 已提交
342 343 344 345 346

      :success
    end
  end

347
  def serialize_widget(merge_request)
348 349 350
    cached_data = serializer.represent(merge_request, serializer: 'poll_cached_widget')
    widget_data = serializer.represent(merge_request, serializer: 'poll_widget')
    cached_data.merge!(widget_data)
351 352
  end

F
Fatih Acet 已提交
353
  def serializer
354
    @serializer ||= MergeRequestSerializer.new(current_user: current_user, project: merge_request.project)
F
Fatih Acet 已提交
355
  end
D
Douwe Maan 已提交
356 357 358 359

  def define_edit_vars
    @source_project = @merge_request.source_project
    @target_project = @merge_request.target_project
A
Alin Ciciu 已提交
360
    @noteable = @merge_request
T
Tiago Botelho 已提交
361 362 363

    # FIXME: We have to assign a presenter to another instance variable
    # due to class_name checks being made with issuable classes
364
    @mr_presenter = @merge_request.present(current_user: current_user)
D
Douwe Maan 已提交
365
  end
J
Jarka Kadlecova 已提交
366

367 368
  def finder_type
    MergeRequestsFinder
J
Jarka Kadlecova 已提交
369
  end
370 371 372 373 374 375 376 377 378 379

  def check_user_can_push_to_source_branch!
    return access_denied! unless @merge_request.source_branch_exists?

    access_check = ::Gitlab::UserAccess
      .new(current_user, project: @merge_request.source_project)
      .can_push_to_branch?(@merge_request.source_branch)

    access_denied! unless access_check
  end
380

381 382 383 384
  def merge_access_check
    access_denied! unless @merge_request.can_be_merged_by?(current_user)
  end

385
  def whitelist_query_limiting
386 387
    # Also see https://gitlab.com/gitlab-org/gitlab-foss/issues/42441
    Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42438')
388
  end
M
Matija Čupić 已提交
389 390 391 392 393 394 395 396 397 398 399 400

  def reports_response(report_comparison)
    case report_comparison[:status]
    when :parsing
      ::Gitlab::PollingInterval.set_header(response, interval: 3000)

      render json: '', status: :no_content
    when :parsed
      render json: report_comparison[:data].to_json, status: :ok
    when :error
      render json: { status_reason: report_comparison[:status_reason] }, status: :bad_request
    else
401
      raise "Failed to build comparison response as comparison yielded unknown status '#{report_comparison[:status]}'"
M
Matija Čupić 已提交
402 403
    end
  end
404

405
  def authorize_read_actual_head_pipeline!
406 407
    return render_404 unless can?(current_user, :read_build, merge_request.actual_head_pipeline)
  end
D
Dmitriy Zaporozhets 已提交
408
end
409 410

Projects::MergeRequestsController.prepend_if_ee('EE::Projects::MergeRequestsController')