Commit d170133b authored by Douwe Maan's avatar Douwe Maan Committed by Robert Speicher

Refactor changing files in web UI

parent bfb635a4
......@@ -35,7 +35,7 @@ export default class BlobFileDropzone {
this.removeFile(file);
});
this.on('sending', function (file, xhr, formData) {
formData.append('target_branch', form.find('input[name="target_branch"]').val());
formData.append('branch_name', form.find('input[name="branch_name"]').val());
formData.append('create_merge_request', form.find('.js-create-merge-request').val());
formData.append('commit_message', form.find('.js-commit-message').val());
});
......
module CreatesCommit
extend ActiveSupport::Concern
def set_start_branch_to_branch_name
branch_exists = @repository.find_branch(@branch_name)
@start_branch = @branch_name if branch_exists
end
def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil)
set_commit_variables
if can?(current_user, :push_code, @project)
@project_to_commit_into = @project
@branch_name ||= @ref
else
@project_to_commit_into = current_user.fork_of(@project)
@branch_name ||= @project_to_commit_into.repository.next_branch('patch')
end
@start_branch ||= @ref || @branch_name
commit_params = @commit_params.merge(
start_project: @mr_target_project,
start_branch: @mr_target_branch,
target_branch: @mr_source_branch
start_project: @project,
start_branch: @start_branch,
branch_name: @branch_name
)
result = service.new(
@mr_source_project, current_user, commit_params).execute
result = service.new(@project_to_commit_into, current_user, commit_params).execute
if result[:status] == :success
update_flash_notice(success_notice)
......@@ -72,30 +84,30 @@ module CreatesCommit
def new_merge_request_path
new_namespace_project_merge_request_path(
@mr_source_project.namespace,
@mr_source_project,
@project_to_commit_into.namespace,
@project_to_commit_into,
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
source_project_id: @project_to_commit_into.id,
target_project_id: @project.id,
source_branch: @branch_name,
target_branch: @start_branch
}
)
end
def existing_merge_request_path
namespace_project_merge_request_path(@mr_target_project.namespace, @mr_target_project, @merge_request)
namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
end
def merge_request_exists?
return @merge_request if defined?(@merge_request)
@merge_request = MergeRequestsFinder.new(current_user, project_id: @mr_target_project.id).execute.opened.
find_by(source_branch: @mr_source_branch, target_branch: @mr_target_branch, source_project_id: @mr_source_project)
@merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.
find_by(source_project_id: @project_to_commit_into, source_branch: @branch_name, target_branch: @start_branch)
end
def different_project?
@mr_source_project != @mr_target_project
@project_to_commit_into != @project
end
def create_merge_request?
......@@ -103,22 +115,6 @@ module CreatesCommit
# as the target branch in the same project,
# we don't want to create a merge request.
params[:create_merge_request].present? &&
(different_project? || @mr_target_branch != @mr_source_branch)
end
def set_commit_variables
if can?(current_user, :push_code, @project)
@mr_source_project = @project
@target_branch ||= @ref
else
@mr_source_project = current_user.fork_of(@project)
@target_branch ||= @mr_source_project.repository.next_branch('patch')
end
# Merge request to this project
@mr_target_project = @project
@mr_target_branch ||= @ref || @target_branch
@mr_source_branch = @target_branch
(different_project? || @start_branch != @branch_name)
end
end
......@@ -89,9 +89,4 @@ class Projects::ApplicationController < ApplicationController
def builds_enabled
return render_404 unless @project.feature_available?(:builds, current_user)
end
def update_ref
branch_exists = @repository.find_branch(@target_branch)
@ref = @target_branch if branch_exists
end
end
......@@ -25,10 +25,10 @@ class Projects::BlobController < Projects::ApplicationController
end
def create
update_ref
set_start_branch_to_branch_name
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)) },
success_path: -> { namespace_project_blob_path(@project.namespace, @project, File.join(@branch_name, @file_path)) },
failure_view: :new,
failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref))
end
......@@ -69,10 +69,10 @@ class Projects::BlobController < Projects::ApplicationController
end
def destroy
create_commit(Files::DestroyService, success_notice: "The file has been successfully deleted.",
success_path: -> { namespace_project_tree_path(@project.namespace, @project, @target_branch) },
failure_view: :show,
failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
create_commit(Files::DeleteService, success_notice: "The file has been successfully deleted.",
success_path: -> { namespace_project_tree_path(@project.namespace, @project, @branch_name) },
failure_view: :show,
failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
end
def diff
......@@ -127,16 +127,16 @@ class Projects::BlobController < Projects::ApplicationController
def after_edit_path
from_merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.find_by(iid: params[:from_merge_request_iid])
if from_merge_request && @target_branch == @ref
if from_merge_request && @branch_name == @ref
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
"##{hexdigest(@path)}"
else
namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path))
namespace_project_blob_path(@project.namespace, @project, File.join(@branch_name, @path))
end
end
def editor_variables
@target_branch = params[:target_branch]
@branch_name = params[:branch_name]
@file_path =
if action_name.to_s == 'create'
......
......@@ -56,9 +56,7 @@ class Projects::CommitController < Projects::ApplicationController
return render_404 if @start_branch.blank?
@target_branch = create_new_branch? ? @commit.revert_branch_name : @start_branch
@mr_target_branch = @start_branch
@branch_name = create_new_branch? ? @commit.revert_branch_name : @start_branch
create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully reverted.",
success_path: -> { successful_change_path }, failure_path: failed_change_path)
......@@ -69,9 +67,7 @@ class Projects::CommitController < Projects::ApplicationController
return render_404 if @start_branch.blank?
@target_branch = create_new_branch? ? @commit.cherry_pick_branch_name : @start_branch
@mr_target_branch = @start_branch
@branch_name = create_new_branch? ? @commit.cherry_pick_branch_name : @start_branch
create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully cherry-picked.",
success_path: -> { successful_change_path }, failure_path: failed_change_path)
......@@ -84,7 +80,7 @@ class Projects::CommitController < Projects::ApplicationController
end
def successful_change_path
referenced_merge_request_url || namespace_project_commits_url(@project.namespace, @project, @target_branch)
referenced_merge_request_url || namespace_project_commits_url(@project.namespace, @project, @branch_name)
end
def failed_change_path
......
......@@ -34,16 +34,16 @@ class Projects::TreeController < Projects::ApplicationController
def create_dir
return render_404 unless @commit_params.values.all?
update_ref
set_start_branch_to_branch_name
create_commit(Files::CreateDirService, success_notice: "The directory has been successfully created.",
success_path: namespace_project_tree_path(@project.namespace, @project, File.join(@target_branch, @dir_name)),
success_path: namespace_project_tree_path(@project.namespace, @project, File.join(@branch_name, @dir_name)),
failure_path: namespace_project_tree_path(@project.namespace, @project, @ref))
end
private
def assign_dir_vars
@target_branch = params[:target_branch]
@branch_name = params[:branch_name]
@dir_name = File.join(@path, params[:dir_name])
@commit_params = {
......
......@@ -272,14 +272,14 @@ module ProjectsHelper
end
end
def add_special_file_path(project, file_name:, commit_message: nil, target_branch: nil, context: nil)
def add_special_file_path(project, file_name:, commit_message: nil, branch_name: nil, context: nil)
namespace_project_new_blob_path(
project.namespace,
project,
project.default_branch || 'master',
file_name: file_name,
commit_message: commit_message || "Add #{file_name.downcase}",
target_branch: target_branch,
branch_name: branch_name,
context: context
)
end
......
......@@ -35,7 +35,7 @@ module TreeHelper
end
def on_top_of_branch?(project = @project, ref = @ref)
project.repository.branch_names.include?(ref)
project.repository.branch_exists?(ref)
end
def can_edit_tree?(project = nil, ref = nil)
......
module Commits
class ChangeService < ::BaseService
ValidationError = Class.new(StandardError)
ChangeError = Class.new(StandardError)
class ChangeService < Commits::CreateService
def initialize(*args)
super
def execute
@start_project = params[:start_project] || @project
@start_branch = params[:start_branch]
@target_branch = params[:target_branch]
@commit = params[:commit]
check_push_permissions
commit
rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError,
ValidationError, ChangeError => ex
error(ex.message)
end
private
def commit
raise NotImplementedError
end
def commit_change(action)
raise NotImplementedError unless repository.respond_to?(action)
validate_target_branch if different_branch?
repository.public_send(
action,
current_user,
@commit,
@target_branch,
@branch_name,
start_project: @start_project,
start_branch_name: @start_branch)
success
rescue Repository::CreateTreeError
error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title(current_user)} automatically.
A #{action.to_s.dasherize} may have already been performed with this #{@commit.change_type_title(current_user)}, or a more recent commit may have updated some of its content."
This #{@commit.change_type_title(current_user)} may already have been #{action.to_s.dasherize}ed, or a more recent commit may have updated some of its content."
raise ChangeError, error_msg
end
def check_push_permissions
allowed = ::Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(@target_branch)
unless allowed
raise ValidationError.new('You are not allowed to push into this branch')
end
true
end
def validate_target_branch
result = ValidateNewBranchService.new(@project, current_user)
.execute(@target_branch)
if result[:status] == :error
raise ChangeError, "There was an error creating the source branch: #{result[:message]}"
end
end
def different_branch?
@start_branch != @target_branch || @start_project != @project
end
end
end
module Commits
class CherryPickService < ChangeService
def commit
def create_commit!
commit_change(:cherry_pick)
end
end
......
module Commits
class CreateService < ::BaseService
ValidationError = Class.new(StandardError)
ChangeError = Class.new(StandardError)
def initialize(*args)
super
@start_project = params[:start_project] || @project
@start_branch = params[:start_branch]
@branch_name = params[:branch_name]
end
def execute
validate!
new_commit = create_commit!
success(result: new_commit)
rescue ValidationError, ChangeError, Gitlab::Git::Index::IndexError, Repository::CommitError, GitHooksService::PreReceiveError => ex
error(ex.message)
end
private
def create_commit!
raise NotImplementedError
end
def raise_error(message)
raise ValidationError, message
end
def different_branch?
@start_branch != @branch_name || @start_project != @project
end
def validate!
validate_permissions!
validate_on_branch!
validate_branch_existance!
validate_new_branch_name! if different_branch?
end
def validate_permissions!
allowed = ::Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(@branch_name)
unless allowed
raise_error("You are not allowed to push into this branch")
end
end
def validate_on_branch!
if !@start_project.empty_repo? && !@start_project.repository.branch_exists?(@start_branch)
raise_error('You can only create or edit files when you are on a branch')
end
end
def validate_branch_existance!
if !project.empty_repo? && different_branch? && repository.branch_exists?(@branch_name)
raise_error("A branch called '#{@branch_name}' already exists. Switch to that branch in order to make changes")
end
end
def validate_new_branch_name!
result = ValidateNewBranchService.new(project, current_user).execute(@branch_name)
if result[:status] == :error
raise_error("Something went wrong when we tried to create '#{@branch_name}' for you: #{result[:message]}")
end
end
end
end
module Commits
class RevertService < ChangeService
def commit
def create_commit!
commit_change(:revert)
end
end
......
module Files
class BaseService < ::BaseService
ValidationError = Class.new(StandardError)
def execute
@start_project = params[:start_project] || @project
@start_branch = params[:start_branch]
@target_branch = params[:target_branch]
class BaseService < Commits::CreateService
def initialize(*args)
super
@author_email = params[:author_email]
@author_name = params[:author_name]
@commit_message = params[:commit_message]
@file_path = params[:file_path]
@previous_path = params[:previous_path]
@file_content = if params[:file_content_encoding] == 'base64'
Base64.decode64(params[:file_content])
else
params[:file_content]
end
@last_commit_sha = params[:last_commit_sha]
@author_email = params[:author_email]
@author_name = params[:author_name]
# Validate parameters
validate
# Create new branch if it different from start_branch
validate_target_branch if different_branch?
result = commit
if result
success(result: result)
else
error('Something went wrong. Your changes were not committed')
end
rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError, ValidationError => ex
error(ex.message)
end
private
def different_branch?
@start_branch != @target_branch || @start_project != @project
end
def file_has_changed?
return false unless @last_commit_sha && last_commit
@last_commit_sha != last_commit.sha
end
def raise_error(message)
raise ValidationError.new(message)
end
def validate
allowed = ::Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(@target_branch)
unless allowed
raise_error("You are not allowed to push into this branch")
end
if !@start_project.empty_repo? && !@start_project.repository.branch_exists?(@start_branch)
raise ValidationError, 'You can only create or edit files when you are on a branch'
end
if !project.empty_repo? && different_branch? && repository.branch_exists?(@branch_name)
raise ValidationError, "A branch called #{@branch_name} already exists. Switch to that branch in order to make changes"
end
end
def validate_target_branch
result = ValidateNewBranchService.new(project, current_user).
execute(@target_branch)
@file_path = params[:file_path]
@previous_path = params[:previous_path]
if result[:status] == :error
raise_error("Something went wrong when we tried to create #{@target_branch} for you: #{result[:message]}")
end
@file_content = params[:file_content]
@file_content = Base64.decode64(@file_content) if params[:file_content_encoding] == 'base64'
end
end
end
module Files
class CreateDirService < Files::BaseService
def commit
def create_commit!
repository.create_dir(
current_user,
@file_path,
message: @commit_message,
branch_name: @target_branch,
branch_name: @branch_name,
author_email: @author_email,
author_name: @author_name,
start_project: @start_project,
start_branch_name: @start_branch)
end
def validate
super
unless @file_path =~ Gitlab::Regex.file_path_regex
raise_error(
'Your changes could not be committed, because the file path ' +
Gitlab::Regex.file_path_regex_message
)
end
end
end
end
module Files
class CreateService < Files::BaseService
def commit
def create_commit!
repository.create_file(
current_user,
@file_path,
@file_content,
message: @commit_message,
branch_name: @target_branch,
branch_name: @branch_name,
author_email: @author_email,
author_name: @author_name,
start_project: @start_project,
start_branch_name: @start_branch)
end
def validate
super
if @file_content.nil?
raise_error("You must provide content.")
end
if @file_path =~ Gitlab::Regex.directory_traversal_regex
raise_error(
'Your changes could not be committed, because the file name ' +
Gitlab::Regex.directory_traversal_regex_message
)
end
unless @file_path =~ Gitlab::Regex.file_path_regex
raise_error(
'Your changes could not be committed, because the file name ' +
Gitlab::Regex.file_path_regex_message
)
end
unless project.empty_repo?
@file_path.slice!(0) if @file_path.start_with?('/')
blob = repository.blob_at_branch(@start_branch, @file_path)
if blob
raise_error('Your changes could not be committed because a file with the same name already exists')
end
end
end
end
end
module Files
class DestroyService < Files::BaseService
def commit
class DeleteService < Files::BaseService
def create_commit!
repository.delete_file(
current_user,
@file_path,
message: @commit_message,
branch_name: @target_branch,
branch_name: @branch_name,
author_email: @author_email,
author_name: @author_name,
start_project: @start_project,
......
module Files
class MultiService < Files::BaseService
FileChangedError = Class.new(StandardError)
ACTIONS = %w[create update delete move].freeze
def commit
def create_commit!
repository.multi_action(
user: current_user,
message: @commit_message,
branch_name: @target_branch,
branch_name: @branch_name,
actions: params[:actions],
author_email: @author_email,
author_name: @author_name,
......@@ -19,122 +15,17 @@ module Files
private
def validate
def validate!
super
params[:actions].each_with_index do |action, index|
if ACTIONS.include?(action[:action].to_s)
action[:action] = action[:action].to_sym
else
raise_error("Unknown action type `#{action[:action]}`.")
end
unless action[:file_path].present?
raise_error("You must specify a file_path.")
end
action[:file_path].slice!(0) if action[:file_path] && action[:file_path].start_with?('/')
action[:previous_path].slice!(0) if action[:previous_path] && action[:previous_path].start_with?('/')
regex_check(action[:file_path])
regex_check(action[:previous_path]) if action[:previous_path]
if project.empty_repo? && action[:action] != :create
raise_error("No files to #{action[:action]}.")
end
validate_file_exists(action)
case action[:action]
when :create
validate_create(action)
when :update
validate_update(action)
when :delete
validate_delete(action)
when :move
validate_move(action, index)
end
end
end
def validate_file_exists(action)
return if action[:action] == :create
file_path = action[:file_path]
file_path = action[:previous_path] if action[:action] == :move
blob = repository.blob_at_branch(params[:branch], file_path)
unless blob
raise_error("File to be #{action[:action]}d `#{file_path}` does not exist.")
params[:actions].each do |action|
validate_action!(action)
end
end
def last_commit
Gitlab::Git::Commit.last_for_path(repository, @start_branch, @file_path)
end
def regex_check(file)
if file =~ Gitlab::Regex.directory_traversal_regex
raise_error(
'Your changes could not be committed, because the file name, `' +
file +
'` ' +
Gitlab::Regex.directory_traversal_regex_message
)
end
unless file =~ Gitlab::Regex.file_path_regex