Commit ae33fdf2 authored by Angus MacArthur's avatar Angus MacArthur
Browse files

updated fork feature to use gitlab-shell for v5 of gitlab

parent f3544ac5636c
module Projects
class ForkContext < BaseContext
include Gitlab::ShellAdapter
def initialize(project, user)
@from_project, @current_user = project, user
end
def execute
project = Project.new
project.initialize_dup(@from_project)
project.name = @from_project.name
project.path = @from_project.path
project.namespace = current_user.namespace
Project.transaction do
#First save the DB entries as they can be rolled back if the repo fork fails
project.creator = current_user
project.build_forked_project_link(forked_to_project_id: project.id, forked_from_project_id: @from_project.id)
if project.save
project.users_projects.create(project_access: UsersProject::MASTER, user: current_user)
end
#Now fork the repo
unless gitlab_shell.fork_repository(@from_project.path_with_namespace, project.namespace.path)
raise "forking failed in gitlab-shell"
end
project.ensure_satellite_exists
end
project
rescue => ex
project.errors.add(:base, "Can't fork project. Please try again later")
project.destroy
end
end
end
......@@ -78,4 +78,19 @@ def destroy
format.html { redirect_to root_path }
end
end
def fork
@project = ::Projects::ForkContext.new(project, current_user).execute
respond_to do |format|
format.html do
if @project.saved? && @project.forked?
redirect_to(@project, notice: 'Project was successfully forked.')
else
render action: "new"
end
end
format.js
end
end
end
......@@ -60,7 +60,8 @@ def project_guest_rules
:read_note,
:write_project,
:write_issue,
:write_note
:write_note,
:fork_project
]
end
......
class ForkedProjectLink < ActiveRecord::Base
attr_accessible :forked_from_project_id, :forked_to_project_id
# Relations
belongs_to :forked_to_project, class_name: Project
belongs_to :forked_from_project, class_name: Project
end
......@@ -43,6 +43,8 @@ class Project < ActiveRecord::Base
has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', foreign_key: 'project_id'
has_one :gitlab_ci_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link
has_many :events, dependent: :destroy
has_many :merge_requests, dependent: :destroy
......@@ -400,4 +402,9 @@ def project_access_human(member)
def protected_branch? branch_name
protected_branches_names.include?(branch_name)
end
def forked?
!(forked_project_link.nil? || forked_project_link.forked_from_project.nil?)
end
end
class ProjectObserver < BaseObserver
def after_create(project)
GitlabShellWorker.perform_async(
:add_repository,
project.path_with_namespace
)
unless project.forked?
GitlabShellWorker.perform_async(
:add_repository,
project.path_with_namespace
)
log_info("#{project.owner.name} created a new project \"#{project.name_with_namespace}\"")
log_info("#{project.owner.name} created a new project \"#{project.name_with_namespace}\"")
end
end
def after_update(project)
......
......@@ -5,6 +5,9 @@
.span4.pull-right
.pull-right
- unless @project.empty_repo?
- if can? current_user, :fork_project, @project
= link_to fork_project_path(@project), title: "Fork", class: "btn small grouped", method: "POST" do
Fork
- if can? current_user, :download_code, @project
= link_to archive_project_repository_path(@project), class: "btn-small btn grouped" do
%i.icon-download-alt
......
......@@ -167,6 +167,7 @@
resources :projects, constraints: { id: /(?:[a-zA-Z.0-9_\-]+\/)?[a-zA-Z.0-9_\-]+/ }, except: [:new, :create, :index], path: "/" do
member do
put :transfer
post :fork
end
resources :blob, only: [:show], constraints: {id: /.+/}
......
class CreateForkedProjectLinks < ActiveRecord::Migration
def change
create_table :forked_project_links do |t|
t.integer :forked_to_project_id, :null => false
t.integer :forked_from_project_id, :null => false
t.timestamps
end
add_index :forked_project_links, :forked_to_project_id, :unique => true
end
end
......@@ -32,6 +32,15 @@
add_index "events", ["target_id"], :name => "index_events_on_target_id"
add_index "events", ["target_type"], :name => "index_events_on_target_type"
create_table "forked_project_links", :force => true do |t|
t.integer "forked_to_project_id", :null => false
t.integer "forked_from_project_id", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "forked_project_links", ["forked_to_project_id"], :name => "index_forked_project_links_on_forked_to_project_id", :unique => true
create_table "issues", :force => true do |t|
t.string "title"
t.integer "assignee_id"
......
Feature: Fork Project
Background:
Given I sign in as a user
And I am a member of project "Shop"
When I visit project "Shop" page
Scenario: User fork a project
Given I click link "Fork"
Then I should see the forked project page
Scenario: User already has forked the project
Given I already have a project named "Shop" in my namespace
And I click link "Fork"
Then I should see a "Name has already been taken" warning
class ForkProject < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
step 'I click link "Fork"' do
Gitlab::Shell.any_instance.stub(:fork_repository).and_return(true)
click_link "Fork"
end
step 'I am a member of project "Shop"' do
@project = Project.find_by_name "Shop"
@project ||= create(:project_with_code, name: "Shop")
@project.team << [@user, :reporter]
end
step 'I should see the forked project page' do
page.should have_content "Project was successfully forked."
current_path.should include current_user.namespace.path
end
step 'I already have a project named "Shop" in my namespace' do
@my_project = create(:project_with_code, name: "Shop", namespace: current_user.namespace)
end
step 'I should see a "Name has already been taken" warning' do
page.should have_content "Name has already been taken"
end
end
\ No newline at end of file
......@@ -36,6 +36,18 @@ def mv_repository(path, new_path)
system("#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects mv-project #{path}.git #{new_path}.git")
end
# Fork repository to new namespace
#
# path - project path with namespace
# fork_namespace - namespace for forked project
#
# Ex.
# fork_repository("gitlab/gitlab-ci", "randx")
#
def fork_repository(path, fork_namespace)
system("#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects fork-project #{path}.git #{fork_namespace}")
end
# Remove repository from file system
#
# name - project path with namespace
......
require 'spec_helper'
describe Projects::ForkContext do
describe :fork_by_user do
before do
@from_user = create :user
@from_project = create(:project, creator_id: @from_user.id)
@to_user = create :user
end
context 'fork project' do
before do
@to_project = fork_project(@from_project, @to_user)
end
it { @to_project.owner.should == @to_user }
it { @to_project.namespace.should == @to_user.namespace }
end
context 'fork project failure' do
before do
#corrupt the project so the attempt to fork will fail
@from_project = create(:project, path: "empty")
@to_project = fork_project(@from_project, @to_user, false)
end
it {@to_project.errors.should_not be_empty}
it {@to_project.errors[:base].should include("Can't fork project. Please try again later") }
end
end
def fork_project(from_project, user, fork_success = true)
context = Projects::ForkContext.new(from_project, user)
shell = mock("gitlab_shell")
shell.stub(fork_repository: fork_success)
context.stub(gitlab_shell: shell)
context.execute
end
end
# Read about factories at https://github.com/thoughtbot/factory_girl
FactoryGirl.define do
factory :forked_project_link do
association :forked_to_project, factory: :project
association :forked_from_project, factory: :project
end
end
......@@ -12,6 +12,7 @@
it { should respond_to :remove_key }
it { should respond_to :add_repository }
it { should respond_to :remove_repository }
it { should respond_to :fork_repository }
it { gitlab_shell.url_to_repo('diaspora').should == Gitlab.config.gitlab_shell.ssh_path_prefix + "diaspora.git" }
end
require 'spec_helper'
describe ForkedProjectLink, "add link on fork" do
let(:project_from) {create(:project)}
let(:namespace) {create(:namespace)}
let(:user) {create(:user, namespace: namespace)}
before do
@project_to = fork_project(project_from, user)
end
it "project_to should know it is forked" do
@project_to.forked?.should be_true
end
it "project should know who it is forked from" do
@project_to.forked_from_project.should == project_from
end
end
describe :forked_from_project do
let(:forked_project_link) {build(:forked_project_link)}
let(:project_from) {create(:project)}
let(:project_to) {create(:project, forked_project_link: forked_project_link)}
before :each do
forked_project_link.forked_from_project = project_from
forked_project_link.forked_to_project = project_to
forked_project_link.save!
end
it "project_to should know it is forked" do
project_to.forked?.should be_true
end
it "project_from should not be forked" do
project_from.forked?.should be_false
end
it "project_to.destroy should destroy fork_link" do
forked_project_link.should_receive(:destroy)
project_to.destroy
end
end
def fork_project(from_project, user)
context = Projects::ForkContext.new(from_project, user)
shell = mock("gitlab_shell")
shell.stub(fork_repository: true)
context.stub(gitlab_shell: shell)
context.execute
end
......@@ -40,6 +40,7 @@
it { should have_many(:deploy_keys).dependent(:destroy) }
it { should have_many(:hooks).dependent(:destroy) }
it { should have_many(:protected_branches).dependent(:destroy) }
it { should have_one(:forked_project_link).dependent(:destroy) }
end
describe "Mass assignment" do
......
......@@ -55,6 +55,7 @@
# projects POST /projects(.:format) projects#create
# new_project GET /projects/new(.:format) projects#new
# fork_project POST /:id/fork(.:format) projects#fork
# wall_project GET /:id/wall(.:format) projects#wall
# files_project GET /:id/files(.:format) projects#files
# edit_project GET /:id/edit(.:format) projects#edit
......@@ -70,6 +71,10 @@
get("/projects/new").should route_to('projects#new')
end
it "to #fork" do
post("/gitlabhq/fork").should route_to('projects#fork', id: 'gitlabhq')
end
it "to #wall" do
get("/gitlabhq/wall").should route_to('walls#show', project_id: 'gitlabhq')
end
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment