From df99ddbba13db4a7699bf1d585675f921cf382ce Mon Sep 17 00:00:00 2001
From: Han Loong Liauw <hanloongliauw@gmail.com>
Date: Tue, 13 Oct 2015 21:24:44 +1100
Subject: [PATCH] Adds ability to remove the forked relationship

This was previously possible through the API but can now be done
through the project#edit settings screen if the current user is
the owner of the project.
Update changelog
---
 CHANGELOG                                    |  2 +
 app/controllers/projects_controller.rb       |  9 ++++-
 app/helpers/projects_helper.rb               |  4 ++
 app/models/ability.rb                        |  3 +-
 app/views/projects/edit.html.haml            | 16 ++++++++
 app/views/projects/remove_fork.js.haml       |  2 +
 config/routes.rb                             |  1 +
 lib/api/projects.rb                          |  2 +-
 spec/controllers/projects_controller_spec.rb | 40 ++++++++++++++++++++
 spec/features/projects_spec.rb               | 29 ++++++++++++--
 10 files changed, 101 insertions(+), 7 deletions(-)
 create mode 100644 app/views/projects/remove_fork.js.haml

diff --git a/CHANGELOG b/CHANGELOG
index a3d796bea66..3c730aef5e4 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -45,6 +45,8 @@ v 8.1.0 (unreleased)
   - Fix position of hamburger in header for smaller screens (Han Loong Liauw)
   - Fix bug where Emojis in Markdown would truncate remaining text (Sakata Sinji)
   - Persist filters when sorting on admin user page (Jerry Lukins)
+  - Adds ability to remove the forked relationship from project settings
+  screen. #2578 (Han Loong Liauw)
 
 v 8.0.4
   - Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu)
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 213c2a7173b..1fb83d0eb6d 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -5,7 +5,7 @@ class ProjectsController < ApplicationController
   before_action :repository, except: [:new, :create]
 
   # Authorize
-  before_action :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive]
+  before_action :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive, :remove_fork]
   before_action :event_filter, only: [:show, :activity]
 
   layout :determine_layout
@@ -64,6 +64,13 @@ class ProjectsController < ApplicationController
     end
   end
 
+  def remove_fork
+    if @project.forked?
+      @project.forked_project_link.destroy
+      flash[:notice] = 'Fork relationship has been removed.'
+    end
+  end
+
   def activity
     respond_to do |format|
       format.html
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index a0220af4c30..b0a3a20fa0a 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -70,6 +70,10 @@ module ProjectsHelper
     "You are going to transfer #{project.name_with_namespace} to another owner. Are you ABSOLUTELY sure?"
   end
 
+  def remove_fork_project_message(project)
+    "You are going to remove the fork relationship to the source project from #{@project.forked_from_project.namespace.try(:name)}.  Are you ABSOLUTELY sure?"
+  end
+
   def project_nav_tabs
     @nav_tabs ||= get_project_nav_tabs(@project, current_user)
   end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index a020b24a550..11ada46610b 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -185,7 +185,8 @@ class Ability
         :change_visibility_level,
         :rename_project,
         :remove_project,
-        :archive_project
+        :archive_project,
+        :remove_fork_project
       ]
     end
 
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 1882a82fba5..ce77a9242fc 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -189,6 +189,21 @@
       - else
         .nothing-here-block Only the project owner can transfer a project
 
+      - if @project.forked? && can?(current_user, :remove_fork_project, @project)
+        = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_namespace_project_path(@project.namespace, @project), method: :put, remote: true, html: { class: 'transfer-project form-horizontal' }) do |f|
+          .panel.panel-default.panel.panel-danger
+            .panel-heading Remove forked relationship
+            .panel-body
+              %p
+                This will remove the relationship to the source project from
+                = link_to project_path(@project.forked_from_project) do
+                  = @project.forked_from_project.namespace.try(:name)
+                %br
+                %strong Once removed it cannot be reversed through this interface
+              = button_to 'Remove forked relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) }
+      - elsif @project.forked?
+        .nothing-here-block Only the project owner can remove the fork relationship
+
       - if can?(current_user, :remove_project, @project)
         .panel.panel-default.panel.panel-danger
           .panel-heading Remove project
@@ -203,6 +218,7 @@
       - else
         .nothing-here-block Only project owner can remove a project
 
+
 .save-project-loader.hide
   .center
     %h2
diff --git a/app/views/projects/remove_fork.js.haml b/app/views/projects/remove_fork.js.haml
new file mode 100644
index 00000000000..17b9fecfeb1
--- /dev/null
+++ b/app/views/projects/remove_fork.js.haml
@@ -0,0 +1,2 @@
+:plain
+    location.href = "#{edit_namespace_project_path(@project.namespace, @project)}";
diff --git a/config/routes.rb b/config/routes.rb
index 8e6fbf6340c..3ac4342b23a 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -378,6 +378,7 @@ Gitlab::Application.routes.draw do
               [:new, :create, :index], path: "/") do
       member do
         put :transfer
+        put :remove_fork
         post :archive
         post :unarchive
         post :toggle_star
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index c2fb36b4143..e8a0e7f3ec9 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -247,7 +247,7 @@ module API
       #  DELETE /projects/:id/fork
       delete ":id/fork" do
         authenticated_as_admin!
-        unless user_project.forked_project_link.nil?
+        if user_project.forked?
           user_project.forked_project_link.destroy
         end
       end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 21beaf37fce..626b56cc789 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -62,4 +62,44 @@ describe ProjectsController do
       expect(user.starred?(public_project)).to be_falsey
     end
   end
+
+  describe "PUT remove_fork" do
+    context 'when signed in' do
+      before do
+        sign_in(user)
+      end
+
+      context 'with forked project' do
+        let(:project_fork) { create(:project, namespace: user.namespace) }
+
+        it 'should remove fork from project' do
+          create(:forked_project_link, forked_to_project: project_fork)
+          put(:remove_fork,
+              namespace_id: project_fork.namespace.to_param,
+              id: project_fork.to_param, format: :js)
+
+          expect(project_fork.forked?).to be_falsey
+          expect(flash[:notice]).to eq('Fork relationship has been removed.')
+          expect(response).to render_template(:remove_fork)
+        end
+      end
+
+      it 'should do nothing if project was not forked' do
+        unforked_project = create(:project, namespace: user.namespace)
+        put(:remove_fork,
+            namespace_id: unforked_project.namespace.to_param,
+            id: unforked_project.to_param, format: :js)
+
+        expect(flash[:notice]).to be_nil
+        expect(response).to render_template(:remove_fork)
+      end
+    end
+
+    it "does nothing if user is not signed in" do
+      put(:remove_fork,
+          namespace_id: project.namespace.to_param,
+          id: project.to_param, format: :js)
+      expect(response.status).to eq(401)
+    end
+  end
 end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index aac93b17a38..df0dcb2bb21 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -34,6 +34,27 @@ feature 'Project', feature: true do
     end
   end
 
+  describe 'remove forked relationship', js: true do
+    let(:user)    { create(:user) }
+    let(:project) { create(:project, namespace: user.namespace) }
+
+    before do
+      login_with user
+      create(:forked_project_link, forked_to_project: project)
+      visit edit_namespace_project_path(project.namespace, project)
+    end
+
+    it 'should remove fork' do
+      expect(page).to have_content 'Remove forked relationship'
+
+      remove_with_confirm('Remove forked relationship', project.path)
+
+      expect(page).to have_content 'Fork relationship has been removed.'
+      expect(project.forked?).to be_falsey
+      expect(page).not_to have_content 'Remove forked relationship'
+    end
+  end
+
   describe 'removal', js: true do
     let(:user)    { create(:user) }
     let(:project) { create(:project, namespace: user.namespace) }
@@ -45,13 +66,13 @@ feature 'Project', feature: true do
     end
 
     it 'should remove project' do
-      expect { remove_project }.to change {Project.count}.by(-1)
+      expect { remove_with_confirm('Remove project', project.path) }.to change {Project.count}.by(-1)
     end
   end
 
-  def remove_project
-    click_button "Remove project"
-    fill_in 'confirm_name_input', with: project.path
+  def remove_with_confirm(button_text, confirm_with)
+    click_button button_text
+    fill_in 'confirm_name_input', with: confirm_with
     click_button 'Confirm'
   end
 end
-- 
GitLab