Commit d9c82d67 authored by Douwe Maan's avatar Douwe Maan

Automatically fork a project when not allowed to edit a file.

parent 110a5f9c
...@@ -35,7 +35,7 @@ class @BlobFileDropzone ...@@ -35,7 +35,7 @@ class @BlobFileDropzone
return return
this.on 'sending', (file, xhr, formData) -> this.on 'sending', (file, xhr, formData) ->
formData.append('new_branch', form.find('.js-new-branch').val()) formData.append('target_branch', form.find('.js-target-branch').val())
formData.append('create_merge_request', form.find('.js-create-merge-request').val()) formData.append('create_merge_request', form.find('.js-create-merge-request').val())
formData.append('commit_message', form.find('.js-commit-message').val()) formData.append('commit_message', form.find('.js-commit-message').val())
return return
......
class @NewCommitForm class @NewCommitForm
constructor: (form) -> constructor: (form) ->
@newBranch = form.find('.js-new-branch') @newBranch = form.find('.js-target-branch')
@originalBranch = form.find('.js-original-branch') @originalBranch = form.find('.js-original-branch')
@createMergeRequest = form.find('.js-create-merge-request') @createMergeRequest = form.find('.js-create-merge-request')
@createMergeRequestContainer = form.find('.js-create-merge-request-container') @createMergeRequestContainer = form.find('.js-create-merge-request-container')
......
module CreatesCommit
extend ActiveSupport::Concern
def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil)
set_commit_variables
commit_params = @commit_params.merge(
source_project: @project,
source_branch: @ref,
target_branch: @target_branch
)
result = service.new(@tree_edit_project, current_user, commit_params).execute
if result[:status] == :success
flash[:notice] = success_notice || "Your changes have been successfully committed."
if create_merge_request?
success_path = new_merge_request_path
target = different_project? ? "project" : "branch"
flash[:notice] << " You can now submit a merge request to get this change into the original #{target}."
end
respond_to do |format|
format.html { redirect_to success_path }
format.json { render json: { message: "success", filePath: success_path } }
end
else
flash[:alert] = result[:message]
respond_to do |format|
format.html do
if failure_view
render failure_view
else
redirect_to failure_path
end
end
format.json { render json: { message: "failed", filePath: failure_path } }
end
end
end
def authorize_edit_tree!
return if can?(current_user, :push_code, project)
return if current_user && current_user.already_forked?(project)
access_denied!
end
private
def new_merge_request_path
new_namespace_project_merge_request_path(
@mr_source_project.namespace,
@mr_source_project,
merge_request: {
source_project_id: @mr_source_project.id,
target_project_id: @mr_target_project.id,
source_branch: @mr_source_branch,
target_branch: @mr_target_branch
}
)
end
def different_project?
@mr_source_project != @mr_target_project
end
def different_branch?
@mr_source_branch != @mr_target_branch || different_project?
end
def create_merge_request?
params[:create_merge_request].present? && different_branch?
end
def set_commit_variables
@mr_source_branch = @target_branch
if can?(current_user, :push_code, @project)
# Edit file in this project
@tree_edit_project = @project
@mr_source_project = @project
if @project.forked?
# Merge request from this project to fork origin
@mr_target_project = @project.forked_from_project
@mr_target_branch = @mr_target_project.repository.root_ref
else
# Merge request to this project
@mr_target_project = @project
@mr_target_branch = @ref
end
else
# Edit file in fork
@tree_edit_project = current_user.fork_of(@project)
# Merge request from fork to this project
@mr_source_project = @tree_edit_project
@mr_target_project = @project
@mr_target_branch = @mr_target_project.repository.root_ref
end
end
end
module CreatesMergeRequestForCommit
extend ActiveSupport::Concern
def new_merge_request_path
if @project.forked?
target_project = @project.forked_from_project || @project
target_branch = target_project.repository.root_ref
else
target_project = @project
target_branch = @ref
end
new_namespace_project_merge_request_path(
@project.namespace,
@project,
merge_request: {
source_project_id: @project.id,
target_project_id: target_project.id,
source_branch: @new_branch,
target_branch: target_branch
}
)
end
def create_merge_request?
params[:create_merge_request] && @new_branch != @ref
end
end
# Controller for viewing a file's blame # Controller for viewing a file's blame
class Projects::BlobController < Projects::ApplicationController class Projects::BlobController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include CreatesMergeRequestForCommit include CreatesCommit
include ActionView::Helpers::SanitizeHelper include ActionView::Helpers::SanitizeHelper
# Raised when given an invalid file path # Raised when given an invalid file path
...@@ -9,21 +9,21 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -9,21 +9,21 @@ class Projects::BlobController < Projects::ApplicationController
before_action :require_non_empty_project, except: [:new, :create] before_action :require_non_empty_project, except: [:new, :create]
before_action :authorize_download_code! before_action :authorize_download_code!
before_action :authorize_push_code!, only: [:destroy, :create] before_action :authorize_edit_tree!, only: [:new, :create, :edit, :update, :destroy]
before_action :assign_blob_vars before_action :assign_blob_vars
before_action :commit, except: [:new, :create] before_action :commit, except: [:new, :create]
before_action :blob, except: [:new, :create] before_action :blob, except: [:new, :create]
before_action :from_merge_request, only: [:edit, :update] before_action :from_merge_request, only: [:edit, :update]
before_action :require_branch_head, only: [:edit, :update] before_action :require_branch_head, only: [:edit, :update]
before_action :editor_variables, except: [:show, :preview, :diff] before_action :editor_variables, except: [:show, :preview, :diff]
before_action :after_edit_path, only: [:edit, :update]
def new def new
commit unless @repository.empty? commit unless @repository.empty?
end end
def create def create
create_commit(Files::CreateService, success_path: after_create_path, create_commit(Files::CreateService, success_notice: "The file has been successfully created.",
success_path: namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)),
failure_view: :new, failure_view: :new,
failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref)) failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref))
end end
...@@ -36,6 +36,14 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -36,6 +36,14 @@ class Projects::BlobController < Projects::ApplicationController
end end
def update def update
after_edit_path =
if from_merge_request && @target_branch == @ref
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
"#file-path-#{hexdigest(@path)}"
else
namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path))
end
create_commit(Files::UpdateService, success_path: after_edit_path, create_commit(Files::UpdateService, success_path: after_edit_path,
failure_view: :edit, failure_view: :edit,
failure_path: namespace_project_blob_path(@project.namespace, @project, @id)) failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
...@@ -50,15 +58,10 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -50,15 +58,10 @@ class Projects::BlobController < Projects::ApplicationController
end end
def destroy def destroy
result = Files::DeleteService.new(@project, current_user, @commit_params).execute create_commit(Files::DeleteService, success_notice: "The file has been successfully deleted.",
success_path: namespace_project_tree_path(@project.namespace, @project, @target_branch),
if result[:status] == :success failure_view: :show,
flash[:notice] = "Your changes have been successfully committed" failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
redirect_to after_destroy_path
else
flash[:alert] = result[:message]
render :show
end
end end
def diff def diff
...@@ -108,74 +111,13 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -108,74 +111,13 @@ class Projects::BlobController < Projects::ApplicationController
render_404 render_404
end end
def create_commit(service, success_path:, failure_view:, failure_path:)
result = service.new(@project, current_user, @commit_params).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
respond_to do |format|
format.html { redirect_to success_path }
format.json { render json: { message: "success", filePath: success_path } }
end
else
flash[:alert] = result[:message]
respond_to do |format|
format.html { render failure_view }
format.json { render json: { message: "failed", filePath: failure_path } }
end
end
end
def after_create_path
@after_create_path ||=
if create_merge_request?
new_merge_request_path
else
namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @file_path))
end
end
def after_edit_path
@after_edit_path ||=
if create_merge_request?
new_merge_request_path
elsif from_merge_request && @new_branch == @ref
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
"#file-path-#{hexdigest(@path)}"
else
namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @path))
end
end
def after_destroy_path
@after_destroy_path ||=
if create_merge_request?
new_merge_request_path
else
namespace_project_tree_path(@project.namespace, @project, @new_branch)
end
end
def from_merge_request def from_merge_request
# If blob edit was initiated from merge request page # If blob edit was initiated from merge request page
@from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id]) @from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id])
end end
def sanitized_new_branch_name
sanitize(strip_tags(params[:new_branch]))
end
def editor_variables def editor_variables
@current_branch = @ref @target_branch = params[:target_branch]
@new_branch =
if params[:new_branch].present?
sanitized_new_branch_name
elsif ::Gitlab::GitAccess.new(current_user, @project).can_push_to_branch?(@ref)
@ref
else
@repository.next_patch_branch
end
@file_path = @file_path =
if action_name.to_s == 'create' if action_name.to_s == 'create'
...@@ -194,8 +136,6 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -194,8 +136,6 @@ class Projects::BlobController < Projects::ApplicationController
@commit_params = { @commit_params = {
file_path: @file_path, file_path: @file_path,
current_branch: @current_branch,
target_branch: @new_branch,
commit_message: params[:commit_message], commit_message: params[:commit_message],
file_content: params[:content], file_content: params[:content],
file_content_encoding: params[:encoding] file_content_encoding: params[:encoding]
......
...@@ -13,16 +13,25 @@ class Projects::ForksController < Projects::ApplicationController ...@@ -13,16 +13,25 @@ class Projects::ForksController < Projects::ApplicationController
@forked_project = ::Projects::ForkService.new(project, current_user, namespace: namespace).execute @forked_project = ::Projects::ForkService.new(project, current_user, namespace: namespace).execute
if @forked_project.saved? && @forked_project.forked? if @forked_project.saved? && @forked_project.forked?
continue_params[:notice] ||= "The project was successfully forked."
if @forked_project.import_in_progress? if @forked_project.import_in_progress?
redirect_to namespace_project_import_path(@forked_project.namespace, @forked_project) redirect_to namespace_project_import_path(@forked_project.namespace, @forked_project, continue: continue_params)
else
if continue_params
redirect_to continue_params[:to], notice: continue_params[:notice]
else else
redirect_to( redirect_to namespace_project_path(@forked_project.namespace, @forked_project)
namespace_project_path(@forked_project.namespace, @forked_project), end
notice: 'Project was successfully forked.'
)
end end
else else
render :error render :error
end end
end end
private
def continue_params
params[:continue].permit(:to, :notice, :notice_now)
end
end end
class Projects::ImportsController < Projects::ApplicationController class Projects::ImportsController < Projects::ApplicationController
# Authorize # Authorize
before_action :authorize_admin_project! before_action :authorize_admin_project!
before_action :require_no_repo before_action :require_no_repo, except: :show
before_action :redirect_if_progress, except: :show before_action :redirect_if_progress, except: :show
def new def new
...@@ -24,21 +24,31 @@ class Projects::ImportsController < Projects::ApplicationController ...@@ -24,21 +24,31 @@ class Projects::ImportsController < Projects::ApplicationController
end end
def show def show
unless @project.import_in_progress? if @project.repository_exists? || @project.import_finished?
if @project.import_finished? if continue_params
redirect_to(project_path(@project)) and return redirect_to continue_params[:to], notice: continue_params[:notice]
else else
redirect_to(new_namespace_project_import_path(@project.namespace, redirect_to project_path(@project)
@project)) and return
end end
elsif @project.import_failed?
redirect_to new_namespace_project_import_path(@project.namespace, @project)
else
if continue_params && continue_params[:notice_now]
flash.now[:notice] = continue_params[:notice_now]
end
# Render
end end
end end
private private
def continue_params
@continue_params ||= params[:continue].permit(:to, :notice, :notice_now)
end
def require_no_repo def require_no_repo
if @project.repository_exists? && !@project.import_in_progress? if @project.repository_exists? && !@project.import_in_progress?
redirect_to(namespace_project_path(@project.namespace, @project)) and return redirect_to(namespace_project_path(@project.namespace, @project))
end end
end end
......
# Controller for viewing a repository's file structure # Controller for viewing a repository's file structure
class Projects::TreeController < Projects::ApplicationController class Projects::TreeController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include CreatesMergeRequestForCommit include CreatesCommit
include ActionView::Helpers::SanitizeHelper include ActionView::Helpers::SanitizeHelper
before_action :require_non_empty_project, except: [:new, :create] before_action :require_non_empty_project, except: [:new, :create]
before_action :assign_ref_vars before_action :assign_ref_vars
before_action :assign_dir_vars, only: [:create_dir] before_action :assign_dir_vars, only: [:create_dir]
before_action :authorize_download_code! before_action :authorize_download_code!
before_action :authorize_push_code!, only: [:create_dir] before_action :authorize_edit_tree!, only: [:create_dir]
def show def show
return render_404 unless @repository.commit(@ref) return render_404 unless @repository.commit(@ref)
...@@ -34,44 +34,20 @@ class Projects::TreeController < Projects::ApplicationController ...@@ -34,44 +34,20 @@ class Projects::TreeController < Projects::ApplicationController
def create_dir def create_dir
return render_404 unless @commit_params.values.all? return render_404 unless @commit_params.values.all?
begin create_commit(Files::CreateDirService, success_notice: "The directory has been successfully created.",
result = Files::CreateDirService.new(@project, current_user, @commit_params).execute success_path: namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @dir_name)),
message = result[:message] failure_path: namespace_project_tree_path(@project.namespace, @project, @ref))
rescue => e
message = e.to_s
end
if result && result[:status] == :success
flash[:notice] = "The directory has been successfully created"
respond_to do |format|
format.html { redirect_to after_create_dir_path }
end
else
flash[:alert] = message
respond_to do |format|
format.html { redirect_to namespace_project_blob_path(@project.namespace, @project, @new_branch) }
end
end
end end
private private
def assign_dir_vars def assign_dir_vars
@new_branch = params[:new_branch].present? ? sanitize(strip_tags(params[:new_branch])) : @ref @target_branch = params[:target_branch]
@dir_name = File.join(@path, params[:dir_name]) @dir_name = File.join(@path, params[:dir_name])
@commit_params = { @commit_params = {
file_path: @dir_name, file_path: @dir_name,
current_branch: @ref,
target_branch: @new_branch,
commit_message: params[:commit_message], commit_message: params[:commit_message],
} }
end end
def after_create_dir_path
if create_merge_request?
new_merge_request_path
else
namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @dir_name))
end
end
end end
...@@ -22,32 +22,92 @@ module BlobHelper ...@@ -22,32 +22,92 @@ module BlobHelper
%w(credits changelog news copying copyright license authors) %w(credits changelog news copying copyright license authors)
end end
def edit_blob_link(project, ref, path, options = {}) def edit_blob_link(project = @project, ref = @ref, path = @path, options = {})
blob = return unless current_user
begin
project.repository.blob_at(ref, path)
rescue
nil
end
return unless blob && blob.text? && blob_editable?(blob) blob = project.repository.blob_at(ref, path) rescue nil
return unless blob && blob_text_editable?(blob)
text = 'Edit'
after = options[:after] || ''
from_mr = options[:from_merge_request_id] from_mr = options[:from_merge_request_id]
link_opts = {} link_opts = {}
link_opts[:from_merge_request_id] = from_mr if from_mr link_opts[:from_merge_request_id] = from_mr if from_mr
cls = 'btn btn-small'
link_to(text, edit_path = namespace_project_edit_blob_path(project.namespace, project,
namespace_project_edit_blob_path(project.namespace, project,
tree_join(ref, path), tree_join(ref, path),
link_opts), link_opts)
class: cls
) + after.html_safe if !on_top_of_branch?
button_tag "Edit", class: "btn btn-default disabled has_tooltip", title: "You can only edit files when you are on a branch", data: {container: 'body'}
elsif can_edit_blob?(blob)
link_to "Edit", edit_path, class: 'btn btn-small'
elsif can?(current_user, :fork_project, project)
continue_params = {
to: edit_path,
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now
}
fork_path = namespace_project_fork_path(project.namespace, project, namespace_key: current_user.namespace.id,
continue: continue_params)
link_to "Edit", fork_path, class: 'btn btn-small', method: :post
end
end
def modify_file_link(project = @project, ref = @ref, path = @path, label:, action:, btn_class:, modal_type:)
return unless current_user
blob = project.repository.blob_at(ref, path) rescue nil
return unless blob
if !on_top_of_branch?
button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "You can only #{action} files when you are on a branch", data: {container: 'body'}
elsif can_edit_blob?(blob)
button_tag label, class: "btn btn-#{btn_class}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal'
elsif can?(current_user, :fork_project, project)
continue_params = {
to: request.fullpath,
notice: edit_in_new_fork_notice + " Try to #{action} this file again.",
notice_now: edit_in_new_fork_notice_now
}
fork_path = namespace_project_fork_path(project.namespace, project, namespace_key: current_user.namespace.id,
continue: continue_params)
link_to label, fork_path, class: "btn btn-#{btn_class}", method: :post
end
end
def replace_blob_link(project = @project, ref = @ref, path = @path)
modify_file_link(
project,
ref,
path,
label: "Replace",
action: "replace",
btn_class: "default",
modal_type: "upload"
)
end
def delete_blob_link(project = @project, ref = @ref, path = @path)
modify_file_link(
project,
ref,
path,
label: "Delete",
action: "delete",
btn_class: "remove",
modal_type: "remove"
)
end
def blob_text_editable?(blob)
blob.text? && !blob.lfs_pointer?
end end
def blob_editable?(blob, project = @project, ref = @ref) def can_edit_blob?(blob, project = @project, ref = @ref)
!blob.lfs_pointer? && allowed_tree_edit?(project, ref) !blob.lfs_pointer? && can_edit_tree?(project, ref)
end end
def leave_edit_message def leave_edit_message
......
...@@ -50,22 +50,47 @@ module TreeHelper ...@@ -50,22 +50,47 @@ module TreeHelper
project.repository.branch_names.include?(ref) project.repository.branch_names.include?(ref)
end end
def allowed_tree_edit?(project = nil, ref = nil) def can_edit_tree?(project = nil, ref = nil)
project ||= @project project ||= @project
ref ||= @ref ref ||= @ref
return false unless on_top_of_branch?(project, ref) return false unless on_top_of_branch?(project, ref)
can?(current_user, :push_code, project) can?(current_user, :push_code, project) ||
(current_user && current_user.already_forked?(project))
end end
def tree_edit_branch(project = @project, ref = @ref) def tree_edit_branch(project = @project, ref = @ref)
if allowed_tree_edit?(project, ref) return unless can_edit_tree?(project, ref)
if can_push_branch?(project, ref) if can_push_branch?(project, ref)
ref ref
else else
project = tree_edit_project(project)
project.repository.next_patch_branch project.repository.next_patch_branch
end end
end end
def tree_edit_project(project = @project)
if can?(current_user, :push_code, project)
project
elsif current_user && current_user.already_forked?(project)
current_user.fork_of(project)
end
end
def edit_in_new_fork_notice_now
"You're not allowed to make changes to this project directly." +
" A fork of this project is being created that you can make changes in, so you can submit a merge request."
end
def edit_in_new_fork_notice
"You're not allowed to make changes to this project directly." +
" A fork of this project has been created that you can make changes in, so you can submit a merge request."
end
def commit_in_fork_help
"A new branch will be created in your fork and a new merge request will be started."
end end
def tree_breadcrumbs(tree, max_links = 2) def tree_breadcrumbs(tree, max_links = 2)
......
...@@ -592,20 +592,30 @@ class Repository ...@@ -592,20 +592,30 @@ class Repository
Gitlab::Popen.popen(args, path_to_repo) Gitlab::Popen.popen(args, path_to_repo)
end end
def with_tmp_ref(oldrev = nil)
random_string = SecureRandom.hex
tmp_ref = "refs/tmp/#{random_string}/head"
if oldrev && !Gitlab::Git.blank_ref?(oldrev)
rugged.references.create(tmp_ref, oldrev)
end
# Make commit in tmp ref
yield(tmp_ref)
ensure
rugged.references.delete(tmp_ref) rescue nil
end
def commit_with_hooks(current_user, branch) def commit_with_hooks(current_user, branch)
oldrev = Gitlab::Git::BLANK_SHA oldrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
was_empty = empty? was_empty = empty?
# Create temporary ref
random_string = SecureRandom.hex
tmp_ref = "refs/tmp/#{random_string}/head"
unless was_empty unless was_empty
oldrev = find_branch(branch).target oldrev = find_branch(branch).target
rugged.references.create(tmp_ref, oldrev)
end end
with_tmp_ref(oldrev) do |tmp_ref|
# Make commit in tmp ref # Make commit in tmp ref
newrev = yield(tmp_ref) newrev = yield(tmp_ref)
...@@ -629,10 +639,7 @@ class Repository ...@@ -629,10 +639,7 @@ class Repository
end end
end end
end end
rescue GitHooksService::PreReceiveError end
# Remove tmp ref and return error to user
rugged.references.delete(tmp_ref)
raise
end end
private private
......
require_relative 'base_service' require_relative 'base_service'
class CreateBranchService < BaseService class CreateBranchService < BaseService
def execute(branch_name, ref) def execute(branch_name, ref, source_project: @project)
valid_branch = Gitlab::GitRefValidator.validate(branch_name) valid_branch = Gitlab::GitRefValidator.validate(branch_name)
if valid_branch == false if valid_branch == false
return error('Branch name invalid') return error('Branch name invalid')
...@@ -13,7 +13,20 @@ class CreateBranchService < BaseService ...@@ -13,7 +13,20 @@ class CreateBranchService < BaseService
return error('Branch already exists') return error('Branch already exists')
end end
new_branch = nil
if source_project != @project
repository.with_tmp_ref do |tmp_ref|
repository.fetch_ref(
source_project.repository.path_to_repo,
"refs/heads/#{ref}",
tmp_ref
)
new_branch = repository.add_branch(current_user, branch_name, tmp_ref)
end
else
new_branch = repository.add_branch(current_user, branch_name, ref) new_branch = repository.add_branch(current_user, branch_name, ref)
end
if new_branch if new_branch
push_data = build_push_data(project, current_user, new_branch) push_data = build_push_data(project, current_user, new_branch)
......
...@@ -3,8 +3,10 @@ module Files ...@@ -3,8 +3,10 @@ module Files
class ValidationError < StandardError; end class ValidationError < StandardError; end
def execute def execute
@current_branch = params[:current_branch] @source_project = params[:source_project] || @project
@source_branch = params[:source_branch]
@target_branch = params[:target_branch] @target_branch = params[:target_branch]
@commit_message = params[:commit_message] @commit_message = params[:commit_message]
@file_path = params[:file_path] @file_path = params[:file_path]
@file_content = if params[:file_content_encoding] == 'base64' @file_content = if params[:file_content_encoding] == 'base64'
...@@ -16,8 +18,8 @@ module Files ...@@ -16,8 +18,8 @@ module Files
# Validate parameters # Validate parameters
validate validate
# Create new branch if it different from current_branch # Create new branch if it different from source_branch
if @target_branch != @current_branch if different_branch?
create_target_branch create_target_branch
end end
...@@ -26,18 +28,14 @@ module Files ...@@ -26,18 +28,14 @@ module Files
else else
error("Something went wrong. Your changes were not committed") error("Something went wrong. Your changes were not committed")
end end
rescue Repository::CommitError, GitHooksService::PreReceiveError, ValidationError => ex rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError, ValidationError => ex
error(ex.message) error(ex.message)
end end
private private
def current_branch def different_branch?
@current_branch ||= params[:current_branch] @source_branch != @target_branch || @source_project != @project
end
def target_branch
@target_branch ||= params[:target_branch]
end end
def raise_error(message) def raise_error(message)
...@@ -52,11 +50,11 @@ module Files ...@@ -52,11 +50,11 @@ module Files
end end
unless project.empty_repo? unless project.empty_repo?
unless repository.branch_names.include?(@current_branch) unless @source_project.repository.branch_names.include?(@source_branch)
raise_error("You can only create or edit files when you are on a branch") raise_error("You can only create or edit files when you are on a branch")
end end
if @current_branch != @target_branch if different_branch?
if repository.branch_names.include?(@target_branch) if repository.branch_names.include?(@target_branch)
raise_error("Branch with such name already exists. You need to switch to this branch in order to make changes") raise_error("Branch with such name already exists. You need to switch to this branch in order to make changes")
end end
...@@ -65,10 +63,10 @@ module Files ...@@ -65,10 +63,10 @@ module Files
end end
def create_target_branch def create_target_branch
result = CreateBranchService.new(project, current_user).execute(@target_branch, @current_branch) result = CreateBranchService.new(project, current_user).execute(@target_branch, @source_branch, source_project: @source_project)
unless result[:status] == :success unless result[:status] == :success
raise_error("Something went wrong when we tried to create #{@target_branch} for you") raise_error("Something went wrong when we tried to create #{@target_branch} for you: #{result[:message]}")
end end
end end
end end
......
...@@ -26,7 +26,7 @@ module Files ...@@ -26,7 +26,7 @@ module Files
unless project.empty_repo? unless project.empty_repo?
@file_path.slice!(0) if @file_path.start_with?('/') @file_path.slice!(0) if @file_path.start_with?('/')
blob = repository.blob_at_branch(@current_branch, @file_path) blob = repository.blob_at_branch(@source_branch, @file_path)
if blob if blob
raise_error("Your changes could not be committed because a file with the same name already exists") raise_error("Your changes could not be committed because a file with the same name already exists")
......
...@@ -2,3 +2,7 @@ ...@@ -2,3 +2,7 @@
= button_tag 'Commit Changes', class: 'btn commit-btn js-commit-button btn-create' = button_tag 'Commit Changes', class: 'btn commit-btn js-commit-button btn-create'
= link_to 'Cancel', cancel_path, = link_to 'Cancel', cancel_path,
class: 'btn btn-cancel', data: {confirm: leave_edit_message} class: 'btn btn-cancel', data: {confirm: leave_edit_message}
- unless can?(current_user, :push_code, @project)
.inline.prepend-left-10
= commit_in_fork_help
...@@ -14,13 +14,8 @@ ...@@ -14,13 +14,8 @@
= link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project, = link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project,
tree_join(@commit.sha, @path)), class: 'btn btn-sm' tree_join(@commit.sha, @path)), class: 'btn btn-sm'
- if blob_editable?(@blob) - if current_user
.btn-group{ role: "group" } .btn-group{ role: "group" }
= edit_blob_link(@project, @ref, @path) = edit_blob_link
%button.btn.btn-default{ 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } Replace = replace_blob_link
%button.btn.btn-remove{ 'data-target' => '#modal-remove-blob', 'data-toggle' => 'modal' } Delete = delete_blob_link
- elsif !on_top_of_branch?
.btn-group{ role: "group" }
%button.btn.btn-default.disabled.has_tooltip{title: "You can only edit files when you are on a branch.", data: {container: 'body'}} Edit
%button.btn.btn-default.disabled.has_tooltip{title: "You can only replace files when you are on a branch.", data: {container: 'body'}} Replace
%button.btn.btn-remove.disabled.has_tooltip{title: "You can only delete files when you are on a branch.", data: {container: 'body'}} Delete
...@@ -17,5 +17,9 @@ ...@@ -17,5 +17,9 @@
= submit_tag "Create directory", class: 'btn btn-create' = submit_tag "Create directory", class: 'btn btn-create'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
- unless can?(current_user, :push_code, @project)
.inline.prepend-left-10
= commit_in_fork_help
:javascript :javascript
new NewCommitForm($('.js-create-dir-form')) new NewCommitForm($('.js-create-dir-form'))
...@@ -20,6 +20,11 @@ ...@@ -20,6 +20,11 @@
= button_tag button_title, class: 'btn btn-small btn-create btn-upload-file', id: 'submit-all' = button_tag button_title, class: 'btn btn-small btn-create btn-upload-file', id: 'submit-all'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
- unless can?(current_user, :push_code, @project)
.inline.prepend-left-10
= commit_in_fork_help
:javascript :javascript
disableButtonIfEmptyField($('.js-upload-blob-form').find('.js-commit-message'), '.btn-upload-file'); disableButtonIfEmptyField($('.js-upload-blob-form').find('.js-commit-message'), '.btn-upload-file');
new BlobFileDropzone($('.js-upload-blob-form'), '#{method}'); new BlobFileDropzone($('.js-upload-blob-form'), '#{method}');
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
= hidden_field_tag 'last_commit', @last_commit = hidden_field_tag 'last_commit', @last_commit
= hidden_field_tag 'content', '', id: "file-content" = hidden_field_tag 'content', '', id: "file-content"
= hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
= render 'projects/commit_button', ref: @ref, cancel_path: @after_edit_path = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id)
:javascript :javascript
blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}") blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}")
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
%div#tree-holder.tree-holder %div#tree-holder.tree-holder
= render 'blob', blob: @blob = render 'blob', blob: @blob
- if blob_editable?(@blob) - if can_edit_blob?(@blob)
= render 'projects/blob/remove' = render 'projects/blob/remove'
- title = "Replace #{@blob.name}" - title = "Replace #{@blob.name}"
......
...@@ -18,10 +18,11 @@ ...@@ -18,10 +18,11 @@
= link_to new_namespace_project_snippet_path(@project.namespace, @project) do = link_to new_namespace_project_snippet_path(@project.namespace, @project) do
= icon('file-text-o fw') = icon('file-text-o fw')
New snippet New snippet
- if can?(current_user, :push_code, @project) - if can?(current_user, :push_code, @project)
%li.divider %li.divider
%li %li
= link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master'), title: 'New file' do = link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do
= icon('file fw') = icon('file fw')
New file New file
%li %li
...@@ -32,3 +33,20 @@ ...@@ -32,3 +33,20 @@
= link_to new_namespace_project_tag_path(@project.namespace, @project) do = link_to new_namespace_project_tag_path(@project.namespace, @project) do
= icon('tags fw') = icon('tags fw')
New tag New tag
- elsif current_user && current_user.already_forked?(@project)
%li.divider
%li
= link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do
= icon('file fw')
New file
- elsif can?(current_user, :fork_project, @project)
%li.divider
%li
- continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master'),
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now }
- fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
continue: continue_params)
= link_to fork_path, method: :post do
= icon('file fw')
New file
...@@ -32,7 +32,8 @@ ...@@ -32,7 +32,8 @@
- if editable_diff?(diff_file) - if editable_diff?(diff_file)
= edit_blob_link(@merge_request.source_project, = edit_blob_link(@merge_request.source_project,
@merge_request.source_branch, diff_file.new_path, @merge_request.source_branch, diff_file.new_path,
after: '&nbsp;', from_merge_request_id: @merge_request.id) from_merge_request_id: @merge_request.id)
&nbsp;
= view_file_btn(diff_commit.id, diff_file, project) = view_file_btn(diff_commit.id, diff_file, project)
......
...@@ -43,4 +43,3 @@ ...@@ -43,4 +43,3 @@
%i.fa.fa-spinner.fa-spin %i.fa.fa-spinner.fa-spin
Forking repository Forking repository
%p Please wait a moment, this page will automatically refresh when ready. %p Please wait a moment, this page will automatically refresh when ready.
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
- if tree.readme - if tree.readme
= render "projects/tree/readme", readme: tree.readme = render "projects/tree/readme", readme: tree.readme
- if allowed_tree_edit? - if can_edit_tree?
= render 'projects/blob/upload', title: 'Upload New File', placeholder: 'Upload new file', button_title: 'Upload file', form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post = render 'projects/blob/upload', title: 'Upload New File', placeholder: 'Upload new file', button_title: 'Upload file', form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post
= render 'projects/blob/new_dir' = render 'projects/blob/new_dir'
......
...@@ -11,14 +11,19 @@ ...@@ -11,14 +11,19 @@
= link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path) = link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
- else - else
= link_to title, '#' = link_to title, '#'
- if allowed_tree_edit?
- if current_user
%li %li
- if !on_top_of_branch?
%span.btn.btn-sm.add-to-tree.disabled.has_tooltip{title: "You can only add files when you are on a branch", data: {container: 'body'}}
= icon('plus')
- elsif can_edit_tree?
%span.dropdown %span.dropdown
%a.dropdown-toggle.btn.btn-sm.add-to-tree{href: '#', "data-toggle" => "dropdown"} %a.dropdown-toggle.btn.btn-sm.add-to-tree{href: '#', "data-toggle" => "dropdown"}
= icon('plus') = icon('plus')
%ul.dropdown-menu %ul.dropdown-menu
%li %li
= link_to namespace_project_new_blob_path(@project.namespace, @project, @id), title: 'Create file', id: 'new-file-link' do = link_to namespace_project_new_blob_path(@project.namespace, @project, @id) do
= icon('pencil fw') = icon('pencil fw')
Create file Create file
%li %li
...@@ -30,7 +35,36 @@ ...@@ -30,7 +35,36 @@
= link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do
= icon('folder fw') = icon('folder fw')
New directory New directory
- elsif !on_top_of_branch? - elsif can?(current_user, :fork_project, @project)
%li %span.dropdown
%span.btn.btn-sm.add-to-tree.disabled.has_tooltip{title: "You can only add files when you are on a branch.", data: {container: 'body'}} %a.dropdown-toggle.btn.btn-sm.add-to-tree{href: '#', "data-toggle" => "dropdown"}
= icon('plus') = icon('plus')
%ul.dropdown-menu
%li
- continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @id),
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now }
- fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
continue: continue_params)
= link_to fork_path, method: :post do
= icon('pencil fw')
Create file
%li
- continue_params = { to: request.fullpath,
notice: edit_in_new_fork_notice + " Try to upload a file again.",
notice_now: edit_in_new_fork_notice_now }
- fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
continue: continue_params)
= link_to fork_path, method: :post do
= icon('file fw')
Upload file
%li.divider
%li
- continue_params = { to: request.fullpath,
notice: edit_in_new_fork_notice + " Try to create a new directory again.",
notice_now: edit_in_new_fork_notice_now }
- fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
continue: continue_params)
= link_to fork_path, method: :post do
= icon('folder fw')
New directory
= render 'shared/commit_message_container', placeholder: placeholder = render 'shared/commit_message_container', placeholder: placeholder
- unless @project.empty_repo? - if @project.empty_repo?
= hidden_field_tag 'target_branch', @ref
- else
- if can?(current_user, :push_code, @project)
.form-group.branch .form-group.branch
= label_tag 'new_branch', 'Target branch', class: 'control-label' = label_tag 'target_branch', 'Target branch', class: 'control-label'
.col-sm-10 .col-sm-10
= text_field_tag 'new_branch', @new_branch || tree_edit_branch, required: true, class: "form-control js-new-branch" = text_field_tag 'target_branch', @target_branch || tree_edit_branch, required: true, class: "form-control js-target-branch"
.js-create-merge-request-container .js-create-merge-request-container
.checkbox .checkbox
...@@ -12,5 +15,8 @@ ...@@ -12,5 +15,8 @@
= label_tag "create_merge_request-#{nonce}" do = label_tag "create_merge_request-#{nonce}" do
= check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}" = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
Start a <strong>new merge request</strong> with these changes Start a <strong>new merge request</strong> with these changes
- else
= hidden_field_tag 'target_branch', @target_branch || tree_edit_branch
= hidden_field_tag 'create_merge_request', 1
= hidden_field_tag 'original_branch', @ref, class: 'js-original-branch' = hidden_field_tag 'original_branch', @ref, class: 'js-original-branch'
...@@ -75,7 +75,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -75,7 +75,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end end
step 'I fill the new branch name' do step 'I fill the new branch name' do
fill_in :new_branch, with: 'new_branch_name', visible: true fill_in :target_branch, with: 'new_branch_name', visible: true
end end
step 'I fill the new file name with an illegal name' do step 'I fill the new file name with an illegal name' do
......
...@@ -7,7 +7,7 @@ module API ...@@ -7,7 +7,7 @@ module API
def commit_params(attrs) def commit_params(attrs)
{ {
file_path: attrs[:file_path], file_path: attrs[:file_path],
current_branch: attrs[:branch_name], source_branch: attrs[:branch_name],
target_branch: attrs[:branch_name], target_branch: attrs[:branch_name],
commit_message: attrs[:commit_message], commit_message: attrs[:commit_message],
file_content: attrs[:content], file_content: attrs[:content],
......
...@@ -98,7 +98,7 @@ describe Projects::TreeController do ...@@ -98,7 +98,7 @@ describe Projects::TreeController do
project_id: project.to_param, project_id: project.to_param,
id: 'master', id: 'master',
dir_name: path, dir_name: path,
new_branch: target_branch, target_branch: target_branch,
commit_message: 'Test commit message') commit_message: 'Test commit message')
end end
...@@ -109,7 +109,7 @@ describe Projects::TreeController do ...@@ -109,7 +109,7 @@ describe Projects::TreeController do
it 'redirects to the new directory' do it 'redirects to the new directory' do
expect(subject). expect(subject).
to redirect_to("/#{project.path_with_namespace}/blob/#{target_branch}/#{path}") to redirect_to("/#{project.path_with_namespace}/blob/#{target_branch}/#{path}")
expect(flash[:notice]).to eq('The directory has been successfully created') expect(flash[:notice]).to eq('The directory has been successfully created.')
end end
end 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