1# frozen_string_literal: true
2
3module ProjectForksHelper
4  def fork_project(project, user = nil, params = {})
5    Gitlab::GitalyClient.allow_n_plus_1_calls do
6      fork_project_direct(project, user, params)
7    end
8  end
9
10  def fork_project_direct(project, user = nil, params = {})
11    # Load the `fork_network` for the project to fork as there might be one that
12    # wasn't loaded yet.
13    project.reload unless project.fork_network
14
15    unless user
16      user = create(:user)
17      project.add_developer(user)
18    end
19
20    unless params[:namespace]
21      params[:namespace] = create(:group)
22      params[:namespace].add_owner(user)
23    end
24
25    namespace = params[:namespace]
26    create_repository = params.delete(:repository)
27
28    unless params[:target_project] || params[:using_service]
29      target_level = [project.visibility_level, namespace.visibility_level].min
30      visibility_level = Gitlab::VisibilityLevel.closest_allowed_level(target_level)
31      # Builds and MRs can't have higher visibility level than repository access level.
32      builds_access_level = [project.builds_access_level, project.repository_access_level].min
33
34      params[:target_project] =
35        create(:project,
36          (:repository if create_repository),
37          visibility_level: visibility_level,
38          builds_access_level: builds_access_level,
39          creator: user, namespace: namespace)
40    end
41
42    service = Projects::ForkService.new(project, user, params)
43
44    # Avoid creating a repository
45    unless create_repository
46      allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
47      shell = double('gitlab_shell', fork_repository: true)
48      allow(service).to receive(:gitlab_shell).and_return(shell)
49    end
50
51    forked_project = service.execute(params[:target_project])
52
53    # Reload the both projects so they know about their newly created fork_network
54    if forked_project.persisted?
55      project.reload
56      forked_project.reload
57    end
58
59    if create_repository
60      # The call to project.repository.after_import in RepositoryForkWorker does
61      # not reset the @exists variable of this forked_project.repository
62      # so we have to explicitly call this method to clear the @exists variable.
63      # of the instance we're returning here.
64      forked_project.repository.expire_content_cache
65    end
66
67    forked_project
68  end
69
70  def fork_project_with_submodules(project, user = nil, params = {})
71    Gitlab::GitalyClient.allow_n_plus_1_calls do
72      forked_project = fork_project_direct(project, user, params)
73      TestEnv.copy_repo(
74        forked_project,
75        bare_repo: TestEnv.forked_repo_path_bare,
76        refs: TestEnv::FORKED_BRANCH_SHA
77      )
78      forked_project.repository.expire_content_cache
79
80      forked_project
81    end
82  end
83end
84