summaryrefslogtreecommitdiff
path: root/app/controllers/projects/forks_controller.rb
blob: 0f00fda4687d6411f787df121d9434a29270067e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# frozen_string_literal: true

class Projects::ForksController < Projects::ApplicationController
  include ContinueParams
  include RendersMemberAccess
  include RendersProjectsList
  include Gitlab::Utils::StrongMemoize

  # Authorize
  before_action :disable_query_limiting, only: [:create]
  before_action :require_non_empty_project
  before_action :authorize_download_code!
  before_action :authenticate_user!, only: [:new, :create]
  before_action :authorize_fork_project!, only: [:new, :create]
  before_action :authorize_fork_namespace!, only: [:create]

  feature_category :source_code_management

  before_action do
    push_frontend_feature_flag(:fork_project_form, @project, default_enabled: :yaml)
  end

  def index
    @total_forks_count    = project.forks.size
    @public_forks_count   = project.forks.public_only.size
    @private_forks_count  = @total_forks_count - project.forks.public_and_internal_only.size
    @internal_forks_count = @total_forks_count - @public_forks_count - @private_forks_count

    @forks = load_forks.page(params[:page])

    prepare_projects_for_rendering(@forks)

    respond_to do |format|
      format.html

      format.json do
        render json: {
          html: view_to_html_string("projects/forks/_projects", projects: @forks)
        }
      end
    end
  end

  def new
    respond_to do |format|
      format.html do
        @own_namespace = current_user.namespace if can_fork_to?(current_user.namespace)
        @project = project
      end

      format.json do
        namespaces = load_namespaces_with_associations - [project.namespace]

        namespaces = [current_user.namespace] + namespaces if
          Feature.enabled?(:fork_project_form, project, default_enabled: :yaml) &&
          can_fork_to?(current_user.namespace)

        render json: {
          namespaces: ForkNamespaceSerializer.new.represent(
            namespaces,
            project: project,
            current_user: current_user,
            memberships: memberships_hash,
            forked_projects: forked_projects_by_namespace(namespaces)
          )
        }
      end
    end
  end

  # rubocop: disable CodeReuse/ActiveRecord
  def create
    @forked_project = fork_namespace.projects.find_by(path: project.path)
    @forked_project = nil unless @forked_project && @forked_project.forked_from_project == project

    @forked_project ||= fork_service.execute

    if !@forked_project.saved? || !@forked_project.forked?
      render :error
    elsif @forked_project.import_in_progress?
      redirect_to project_import_path(@forked_project, continue: continue_params)
    elsif continue_params[:to]
      redirect_to continue_params[:to], notice: continue_params[:notice]
    else
      redirect_to project_path(@forked_project), notice: "The project '#{@forked_project.name}' was successfully forked."
    end
  end

  private

  def can_fork_to?(namespace)
    ForkTargetsFinder.new(@project, current_user).execute.id_in(current_user.namespace).any?
  end

  def load_forks
    forks = ForkProjectsFinder.new(
      project,
      params: params.merge(search: params[:filter_projects]),
      current_user: current_user
    ).execute

    forks.includes(:route, :creator, :group, namespace: [:route, :owner])
  end

  def fork_service
    strong_memoize(:fork_service) do
      ::Projects::ForkService.new(project, current_user, fork_params)
    end
  end

  def fork_namespace
    strong_memoize(:fork_namespace) do
      Namespace.find(params[:namespace_key]) if params[:namespace_key].present?
    end
  end

  def fork_params
    params.permit(:path, :name, :description, :visibility).tap do |param|
      param[:namespace] = fork_namespace
    end
  end

  def authorize_fork_namespace!
    access_denied! unless fork_namespace && fork_service.valid_fork_target?
  end

  def disable_query_limiting
    Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20783')
  end

  def load_namespaces_with_associations
    @load_namespaces_with_associations ||= fork_service.valid_fork_targets(only_groups: true).preload(:route)
  end

  def memberships_hash
    current_user.members.where(source: load_namespaces_with_associations).index_by(&:source_id)
  end

  def forked_projects_by_namespace(namespaces)
    project.forks.where(namespace: namespaces).includes(:namespace).index_by(&:namespace_id)
  end
end

Projects::ForksController.prepend_mod_with('Projects::ForksController')