Merge branch 'fork-to-group' into 'master'
Fork to group Fixes #1592 * project can be forked into group * fork link always lead to fork page where you select namespace where to fork See merge request !1253
This commit is contained in:
commit
a4e98f0ec9
|
@ -50,6 +50,7 @@ v 7.4.0
|
|||
- Do not delete tmp/repositories itself during clean-up, only its contents
|
||||
- Support for backup uploads to remote storage
|
||||
- Prevent notes polling when there are not notes
|
||||
- Internal ForkService: Prepare support for fork to a given namespace
|
||||
- API: Add support for forking a project via the API (Bernhard Kaindl)
|
||||
- API: filter project issues by milestone (Julien Bianchi)
|
||||
- Fail harder in the backup script
|
||||
|
|
|
@ -75,6 +75,8 @@ class Dispatcher
|
|||
# Ensure we don't create a particular shortcut handler here. This is
|
||||
# already created, where the network graph is created.
|
||||
shortcut_handler = true
|
||||
when 'projects:forks:new'
|
||||
new ProjectFork()
|
||||
when 'users:show'
|
||||
new User()
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class @ProjectFork
|
||||
constructor: ->
|
||||
$('.fork-thumbnail a').on 'click', ->
|
||||
$('.fork-namespaces').hide()
|
||||
$('.save-project-loader').show()
|
|
@ -270,3 +270,28 @@ ul.nav.nav-projects-tabs {
|
|||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.fork-namespaces {
|
||||
.thumbnail {
|
||||
|
||||
&.fork-exists-thumbnail {
|
||||
border-color: #EEE;
|
||||
|
||||
.caption {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
&.fork-thumbnail {
|
||||
border-color: #AAA;
|
||||
|
||||
&:hover {
|
||||
background-color: $hover;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
class Projects::ForksController < Projects::ApplicationController
|
||||
# Authorize
|
||||
before_filter :authorize_download_code!
|
||||
before_filter :require_non_empty_project
|
||||
|
||||
def new
|
||||
@namespaces = current_user.manageable_namespaces
|
||||
@namespaces.delete(@project.namespace)
|
||||
end
|
||||
|
||||
def create
|
||||
namespace = Namespace.find(params[:namespace_id])
|
||||
@forked_project = ::Projects::ForkService.new(project, current_user, namespace: namespace).execute
|
||||
|
||||
if @forked_project.saved? && @forked_project.forked?
|
||||
redirect_to(@forked_project, notice: 'Project was successfully forked.')
|
||||
else
|
||||
@title = 'Fork project'
|
||||
render :error
|
||||
end
|
||||
end
|
||||
end
|
|
@ -111,22 +111,6 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def fork
|
||||
@forked_project = ::Projects::ForkService.new(project, current_user).execute
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
if @forked_project.saved? && @forked_project.forked?
|
||||
redirect_to(@forked_project, notice: 'Project was successfully forked.')
|
||||
else
|
||||
@title = 'Fork project'
|
||||
render "fork"
|
||||
end
|
||||
end
|
||||
format.js
|
||||
end
|
||||
end
|
||||
|
||||
def autocomplete_sources
|
||||
note_type = params['type']
|
||||
note_id = params['type_id']
|
||||
|
|
|
@ -25,4 +25,12 @@ module NamespacesHelper
|
|||
|
||||
hidden_field_tag(id, value, class: css_class)
|
||||
end
|
||||
|
||||
def namespace_icon(namespace, size = 40)
|
||||
if namespace.kind_of?(Group)
|
||||
group_icon(namespace.path)
|
||||
else
|
||||
avatar_icon(namespace.owner.email, size)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -90,4 +90,8 @@ class Namespace < ActiveRecord::Base
|
|||
def kind
|
||||
type == 'Group' ? 'group' : 'user'
|
||||
end
|
||||
|
||||
def find_fork_of(project)
|
||||
projects.joins(:forked_project_link).where('forked_project_links.forked_from_project_id = ?', project.id).first
|
||||
end
|
||||
end
|
||||
|
|
|
@ -551,4 +551,14 @@ class User < ActiveRecord::Base
|
|||
UsersStarProject.create!(project: project, user: self)
|
||||
end
|
||||
end
|
||||
|
||||
def manageable_namespaces
|
||||
@manageable_namespaces ||=
|
||||
begin
|
||||
namespaces = []
|
||||
namespaces << namespace
|
||||
namespaces += owned_groups
|
||||
namespaces += masters_groups
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,11 +2,9 @@ module Projects
|
|||
class ForkService < BaseService
|
||||
include Gitlab::ShellAdapter
|
||||
|
||||
def initialize(project, user)
|
||||
@from_project, @current_user = project, user
|
||||
end
|
||||
|
||||
def execute
|
||||
@from_project = @project
|
||||
|
||||
project_params = {
|
||||
visibility_level: @from_project.visibility_level,
|
||||
description: @from_project.description,
|
||||
|
@ -15,8 +13,18 @@ module Projects
|
|||
project = Project.new(project_params)
|
||||
project.name = @from_project.name
|
||||
project.path = @from_project.path
|
||||
project.namespace = current_user.namespace
|
||||
project.creator = current_user
|
||||
project.creator = @current_user
|
||||
|
||||
if namespace = @params[:namespace]
|
||||
project.namespace = namespace
|
||||
else
|
||||
project.namespace = @current_user.namespace
|
||||
end
|
||||
|
||||
unless @current_user.can?(:create_projects, project.namespace)
|
||||
project.errors.add(:namespace, 'insufficient access rights')
|
||||
return project
|
||||
end
|
||||
|
||||
# If the project cannot save, we do not want to trigger the project destroy
|
||||
# as this can have the side effect of deleting a repo attached to an existing
|
||||
|
@ -27,7 +35,7 @@ module Projects
|
|||
#First save the DB entries as they can be rolled back if the repo fork fails
|
||||
project.build_forked_project_link(forked_to_project_id: project.id, forked_from_project_id: @from_project.id)
|
||||
if project.save
|
||||
project.team << [current_user, :master]
|
||||
project.team << [@current_user, :master]
|
||||
end
|
||||
#Now fork the repo
|
||||
unless gitlab_shell.fork_repository(@from_project.path_with_namespace, project.namespace.path)
|
||||
|
@ -42,8 +50,8 @@ module Projects
|
|||
else
|
||||
project.errors.add(:base, "Invalid fork destination")
|
||||
end
|
||||
project
|
||||
|
||||
project
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
- unless @project.empty_repo?
|
||||
.fork-buttons
|
||||
- if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace
|
||||
- if current_user.already_forked?(@project)
|
||||
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
|
||||
= link_to project_path(current_user.fork_of(@project)), title: 'Go to my fork' do
|
||||
= link_to_toggle_fork
|
||||
- else
|
||||
= link_to fork_project_path(@project), title: "Fork project", method: "POST" do
|
||||
= link_to new_project_fork_path(@project), title: "Fork project" do
|
||||
= link_to_toggle_fork
|
||||
|
||||
.star-buttons
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
.alert.alert-danger.alert-block
|
||||
%h4
|
||||
%i.fa.fa-code-fork
|
||||
Fork Error!
|
||||
%p
|
||||
You tried to fork
|
||||
= link_to_project @project
|
||||
but it failed for the following reason:
|
||||
|
||||
|
||||
- if @forked_project && @forked_project.errors.any?
|
||||
%p
|
||||
–
|
||||
= @forked_project.errors.full_messages.first
|
||||
|
||||
%p
|
||||
= link_to fork_project_path(@project), title: "Fork", class: "btn", method: "POST" do
|
||||
%i.fa.fa-code-fork
|
||||
Try to Fork again
|
|
@ -0,0 +1,20 @@
|
|||
- if @forked_project && !@forked_project.saved?
|
||||
.alert.alert-danger.alert-block
|
||||
%h4
|
||||
%i.fa.fa-code-fork
|
||||
Fork Error!
|
||||
%p
|
||||
You tried to fork
|
||||
= link_to_project @project
|
||||
but it failed for the following reason:
|
||||
|
||||
|
||||
- if @forked_project && @forked_project.errors.any?
|
||||
%p
|
||||
–
|
||||
= @forked_project.errors.full_messages.first
|
||||
|
||||
%p
|
||||
= link_to new_project_fork_path(@project), title: "Fork", class: "btn" do
|
||||
%i.fa.fa-code-fork
|
||||
Try to Fork again
|
|
@ -0,0 +1,38 @@
|
|||
%h3.page-title Fork project
|
||||
%p.lead Select namespace where to fork this project
|
||||
%hr
|
||||
|
||||
.fork-namespaces
|
||||
- @namespaces.in_groups_of(6, false) do |group|
|
||||
.row
|
||||
- group.each do |namespace|
|
||||
.col-md-2.col-sm-3
|
||||
- if fork = namespace.find_fork_of(@project)
|
||||
.thumbnail.fork-exists-thumbnail
|
||||
= link_to project_path(fork), title: "Visit project fork", class: 'has_tooltip' do
|
||||
= image_tag namespace_icon(namespace, 200)
|
||||
.caption
|
||||
%h4=namespace.human_name
|
||||
%p
|
||||
= namespace.path
|
||||
- else
|
||||
.thumbnail.fork-thumbnail
|
||||
= link_to project_fork_path(@project, namespace_id: namespace.id), title: "Fork here", method: "POST", class: 'has_tooltip' do
|
||||
= image_tag namespace_icon(namespace, 200)
|
||||
.caption
|
||||
%h4=namespace.human_name
|
||||
%p
|
||||
= namespace.path
|
||||
|
||||
%p.light
|
||||
Fork is a copy of a project repository.
|
||||
%br
|
||||
Forking a repository allows you to do changes without affecting the original project.
|
||||
|
||||
.save-project-loader.hide
|
||||
.center
|
||||
%h2
|
||||
%i.fa.fa-spinner.fa-spin
|
||||
Forking repository
|
||||
%p Please wait a moment, this page will automatically refresh when ready.
|
||||
|
|
@ -181,7 +181,6 @@ Gitlab::Application.routes.draw do
|
|||
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
|
||||
post :archive
|
||||
post :unarchive
|
||||
post :upload_image
|
||||
|
@ -214,11 +213,11 @@ Gitlab::Application.routes.draw do
|
|||
|
||||
match "/compare/:from...:to" => "compare#show", as: "compare", via: [:get, :post], constraints: {from: /.+/, to: /.+/}
|
||||
|
||||
resources :snippets, constraints: {id: /\d+/} do
|
||||
member do
|
||||
get "raw"
|
||||
end
|
||||
resources :snippets, constraints: {id: /\d+/} do
|
||||
member do
|
||||
get "raw"
|
||||
end
|
||||
end
|
||||
|
||||
resources :wikis, only: [:show, :edit, :destroy, :create], constraints: {id: /[a-zA-Z.0-9_\-\/]+/} do
|
||||
collection do
|
||||
|
@ -232,6 +231,8 @@ Gitlab::Application.routes.draw do
|
|||
end
|
||||
end
|
||||
|
||||
resource :fork, only: [:new, :create]
|
||||
|
||||
resource :repository, only: [:show] do
|
||||
member do
|
||||
get "archive", constraints: { format: Gitlab::Regex.archive_formats_regex }
|
||||
|
|
|
@ -6,9 +6,11 @@ Feature: Project Fork
|
|||
|
||||
Scenario: User fork a project
|
||||
Given I click link "Fork"
|
||||
When I fork to my namespace
|
||||
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"
|
||||
When I fork to my namespace
|
||||
Then I should see a "Name has already been taken" warning
|
||||
|
|
|
@ -25,4 +25,10 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
|
|||
step 'I should see a "Name has already been taken" warning' do
|
||||
page.should have_content "Name has already been taken"
|
||||
end
|
||||
|
||||
step 'I fork to my namespace' do
|
||||
within '.fork-namespaces' do
|
||||
click_link current_user.name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -55,7 +55,6 @@ end
|
|||
|
||||
# projects POST /projects(.:format) projects#create
|
||||
# new_project GET /projects/new(.:format) projects#new
|
||||
# fork_project POST /:id/fork(.:format) projects#fork
|
||||
# files_project GET /:id/files(.:format) projects#files
|
||||
# edit_project GET /:id/edit(.:format) projects#edit
|
||||
# project GET /:id(.:format) projects#show
|
||||
|
@ -70,10 +69,6 @@ describe ProjectsController, "routing" do
|
|||
get("/projects/new").should route_to('projects#new')
|
||||
end
|
||||
|
||||
it "to #fork" do
|
||||
post("/gitlab/gitlabhq/fork").should route_to('projects#fork', id: 'gitlab/gitlabhq')
|
||||
end
|
||||
|
||||
it "to #edit" do
|
||||
get("/gitlab/gitlabhq/edit").should route_to('projects#edit', id: 'gitlab/gitlabhq')
|
||||
end
|
||||
|
@ -462,3 +457,13 @@ describe Projects::GraphsController, "routing" do
|
|||
get("/gitlab/gitlabhq/graphs/master").should route_to('projects/graphs#show', project_id: 'gitlab/gitlabhq', id: 'master')
|
||||
end
|
||||
end
|
||||
|
||||
describe Projects::ForksController, "routing" do
|
||||
it "to #new" do
|
||||
get("/gitlab/gitlabhq/fork/new").should route_to("projects/forks#new", project_id: 'gitlab/gitlabhq')
|
||||
end
|
||||
|
||||
it "to #create" do
|
||||
post("/gitlab/gitlabhq/fork").should route_to("projects/forks#create", project_id: 'gitlab/gitlabhq')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -42,10 +42,54 @@ describe Projects::ForkService do
|
|||
end
|
||||
end
|
||||
|
||||
def fork_project(from_project, user, fork_success = true)
|
||||
context = Projects::ForkService.new(from_project, user)
|
||||
shell = double("gitlab_shell")
|
||||
shell.stub(fork_repository: fork_success)
|
||||
describe :fork_to_namespace do
|
||||
before do
|
||||
@group_owner = create(:user)
|
||||
@developer = create(:user)
|
||||
@project = create(:project, creator_id: @group_owner.id,
|
||||
star_count: 777,
|
||||
description: 'Wow, such a cool project!')
|
||||
@group = create(:group)
|
||||
@group.add_user(@group_owner, GroupMember::OWNER)
|
||||
@group.add_user(@developer, GroupMember::DEVELOPER)
|
||||
@opts = { namespace: @group }
|
||||
end
|
||||
|
||||
context 'fork project for group' do
|
||||
it 'group owner successfully forks project into the group' do
|
||||
to_project = fork_project(@project, @group_owner, true, @opts)
|
||||
to_project.owner.should == @group
|
||||
to_project.namespace.should == @group
|
||||
to_project.name.should == @project.name
|
||||
to_project.path.should == @project.path
|
||||
to_project.description.should == @project.description
|
||||
to_project.star_count.should be_zero
|
||||
end
|
||||
end
|
||||
|
||||
context 'fork project for group when user not owner' do
|
||||
it 'group developer should fail to fork project into the group' do
|
||||
to_project = fork_project(@project, @developer, true, @opts)
|
||||
to_project.errors[:namespace].should == ['insufficient access rights']
|
||||
end
|
||||
end
|
||||
|
||||
context 'project already exists in group' do
|
||||
it 'should fail due to validation, not transaction failure' do
|
||||
existing_project = create(:project, name: @project.name,
|
||||
namespace: @group)
|
||||
to_project = fork_project(@project, @group_owner, true, @opts)
|
||||
existing_project.persisted?.should be_true
|
||||
to_project.errors[:base].should == ['Invalid fork destination']
|
||||
to_project.errors[:name].should == ['has already been taken']
|
||||
to_project.errors[:path].should == ['has already been taken']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def fork_project(from_project, user, fork_success = true, params = {})
|
||||
context = Projects::ForkService.new(from_project, user, params)
|
||||
shell = double('gitlab_shell').stub(fork_repository: fork_success)
|
||||
context.stub(gitlab_shell: shell)
|
||||
context.execute
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue