From ea5a006f27cfd3013f94652e0e0f0e63091036ad Mon Sep 17 00:00:00 2001
From: Angus MacArthur <amacarthur@blackberry.com>
Date: Thu, 27 Jun 2013 17:49:26 -0400
Subject: [PATCH] Additon of apis for fork administration.

Added ability to add and remove the forked from/to relatioinship
between existing repos.
---
 doc/api/projects.md                | 25 +++++++++++
 lib/api/entities.rb                |  7 ++++
 lib/api/helpers.rb                 |  6 +--
 lib/api/projects.rb                | 36 ++++++++++++++++
 spec/requests/api/projects_spec.rb | 67 ++++++++++++++++++++++++++++++
 5 files changed, 138 insertions(+), 3 deletions(-)

diff --git a/doc/api/projects.md b/doc/api/projects.md
index 323c0be63a4..41b6b6add39 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -453,3 +453,28 @@ Parameters:
 + `id` (required) - The ID of the project.
 + `branch` (required) - The name of the branch.
 
+
+## Admin fork relation
+
+Allows modification of the forked relationship between existing projects. . Available only for admins.
+
+### Create a forked from/to relation between existing projects.
+
+```
+POST /projects/:id/fork/:forked_from_id
+```
+
+Parameters:
+
++ `id` (required) - The ID of the project
++ `forked_from_id:` (required) - The ID of the project that was forked from
+
+### Delete an existing forked from relationship
+
+```
+DELETE /projects/:id/fork
+```
+
+Parameter:
+
++ `id` (required) - The ID of the project
\ No newline at end of file
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 0d8cac5c8fd..dea5771d6b6 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -25,6 +25,12 @@ module API
       expose :id, :url, :created_at
     end
 
+    class ForkedFromProject < Grape::Entity
+      expose :id
+      expose :name, :name_with_namespace
+      expose :path, :path_with_namespace
+    end
+
     class Project < Grape::Entity
       expose :id, :description, :default_branch, :public, :ssh_url_to_repo, :http_url_to_repo, :web_url
       expose :owner, using: Entities::UserBasic
@@ -32,6 +38,7 @@ module API
       expose :path, :path_with_namespace
       expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :created_at, :last_activity_at
       expose :namespace
+      expose :forked_from_project, using: Entities::ForkedFromProject, :if => lambda{ | project, options | project.forked? }
     end
 
     class ProjectMember < UserBasic
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 94cf4f2e69f..f857d4133b2 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -5,12 +5,12 @@ module API
     end
 
     def user_project
-      @project ||= find_project
+      @project ||= find_project(params[:id])
       @project || not_found!
     end
 
-    def find_project
-      project = Project.find_by_id(params[:id]) || Project.find_with_namespace(params[:id])
+    def find_project(id)
+      project = Project.find_by_id(id) || Project.find_with_namespace(id)
 
       if project && can?(current_user, :read_project, project)
         project
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 6dc051e4ba2..d5709f5cb59 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -121,6 +121,42 @@ module API
       end
 
 
+      # Mark this project as forked from another
+      #
+      # Parameters:
+      #   id: (required) - The ID of the project being marked as a fork
+      #   forked_from_id: (required) - The ID of the project it was forked from
+      # Example Request:
+      #   POST /projects/:id/fork/:forked_from_id
+      post ":id/fork/:forked_from_id" do
+        authenticated_as_admin!
+        forked_from_project = find_project(params[:forked_from_id])
+        unless forked_from_project.nil?
+          if user_project.forked_from_project.nil?
+            user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id)
+          else
+            render_api_error!("Project already forked", 409)
+          end
+        else
+          not_found!
+        end
+
+      end
+
+      # Remove a forked_from relationship
+      #
+      # Parameters:
+      # id: (required) - The ID of the project being marked as a fork
+      # Example Request:
+      #  DELETE /projects/:id/fork
+      delete ":id/fork" do
+        authenticated_as_admin!
+        unless user_project.forked_project_link.nil?
+          user_project.forked_project_link.destroy
+        end
+      end
+
+
       # Get a project team members
       #
       # Parameters:
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 31075149647..a6612af83eb 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -595,4 +595,71 @@ describe API::API do
       end
     end
   end
+
+  describe :fork_admin do
+    let(:project_fork_target) { create(:project) }
+    let(:project_fork_source) { create(:project, public: true) }
+
+    describe "POST /projects/:id/fork/:forked_from_id" do
+      let(:new_project_fork_source) { create(:project, public: true) }
+
+      it "shouldn't available for non admin users" do
+        post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user)
+        response.status.should == 403
+      end
+
+      it "should allow project to be forked from an existing project" do
+        project_fork_target.forked?.should_not be_true
+        post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
+        response.status.should == 201
+        project_fork_target.reload
+        project_fork_target.forked_from_project.id.should == project_fork_source.id
+        project_fork_target.forked_project_link.should_not be_nil
+        project_fork_target.forked?.should be_true
+      end
+
+      it "should fail if forked_from project which does not exist" do
+        post api("/projects/#{project_fork_target.id}/fork/9999", admin)
+        response.status.should == 404
+      end
+
+      it "should fail with 409 if already forked" do
+        post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
+        project_fork_target.reload
+        project_fork_target.forked_from_project.id.should == project_fork_source.id
+        post api("/projects/#{project_fork_target.id}/fork/#{new_project_fork_source.id}", admin)
+        response.status.should == 409
+        project_fork_target.reload
+        project_fork_target.forked_from_project.id.should == project_fork_source.id
+        project_fork_target.forked?.should be_true
+      end
+    end
+
+    describe "DELETE /projects/:id/fork" do
+
+      it "shouldn't available for non admin users" do
+        delete api("/projects/#{project_fork_target.id}/fork", user)
+        response.status.should == 403
+      end
+
+      it "should make forked project unforked" do
+        post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
+        project_fork_target.reload
+        project_fork_target.forked_from_project.should_not be_nil
+        project_fork_target.forked?.should be_true
+        delete api("/projects/#{project_fork_target.id}/fork", admin)
+        response.status.should == 200
+        project_fork_target.reload
+        project_fork_target.forked_from_project.should be_nil
+        project_fork_target.forked?.should_not be_true
+      end
+
+      it "should be idempotent if not forked" do
+        project_fork_target.forked_from_project.should be_nil
+        delete api("/projects/#{project_fork_target.id}/fork", admin)
+        response.status.should == 200
+        project_fork_target.reload.forked_from_project.should be_nil
+      end
+    end
+  end
 end
-- 
GitLab