GitLab forking sometimes will not work due to wide fork_network search.

I got this problem on my self-hosted Gitlab on 14.6.7 and I just checked the latest source code and it seems not be fixed on the main branch so I raise this ticket. I did not try to reproduce it on the latest branch but I can reproduce this on the self-hosted GitLab server.

=== symptoms ===

Suppose a user has a few projects on his namespace.

  1. project 2-1.
  2. project 2-2.
  3. random projects. ...

Now this user wants to fork project 1-3. project 1-3 is a child of project root.

project root has the child project like project1-1, project 1-2, project 1-3.

project 1-1 has a child project as project 2-1. project 1-2 has a child project as project 2-2.

Now when the user was trying to fork from project 1-3, GitLab will try to find the fork_network of project 1-3, meaning that it will find project 1-1, project 1-2, project 2-1, project 2-2, project root, and so on.

In these projects, it will also compare if any project exists under the user which means project 2-1 and project 2-2. If it is, then the code thinks it has found a fork of this project. However, this should not be true because project 1-3 has never been forked before.

=== revelent code ===

app/models/manespace.rb

def find_fork_of(project)
    return unless project.fork_network

    if Gitlab::SafeRequestStore.active?
      forks_in_namespace = Gitlab::SafeRequestStore.fetch("namespaces:#{id}:forked_projects") do
        Hash.new do |found_forks, project|
          found_forks[project] = project.fork_network.find_forks_in(projects).first
        end
      end

      forks_in_namespace[project]
    else
      project.fork_network.find_forks_in(projects).first
    end
  end

It says when it is trying to find the fork of the project, it will search this project's fork_network instead of the project and its child only.

== impact ==

It should be pretty small as there is a workaround for us to do.

app/views/projects/buttons/_fork.html.haml

      - if current_user.already_forked?(@project) && current_user.forkable_namespaces.size < 2
        = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: s_('ProjectOverview|Go to your fork'), class: 'gl-button btn btn-default btn-sm has-tooltip fork-btn' do
          = sprite_icon('fork', css_class: 'icon')
          %span= s_('ProjectOverview|Fork')
      - else

The file will also check if the size of a user's namespace is smaller than 2 so I think I can just let the user to be able to join another group to avoid the check.

Edited by nobody