Commit 76b7e24f authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'edit-on-fork' into 'master'

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

Fixes #3215.

To do:

- [ ] Add tests

-----

## "Edit" button on file in a project the user does NOT have write access to

![edit_file](/uploads/7602157420768aef483a6586bba2d164/edit_file.png)

## Clicking will automatically create a fork

![during_fork](/uploads/32f4f5dff9f24ea99522000b0bf881c5/during_fork.png)

## When the fork has been created, the user is returned to the edit page on the original project with a notice

![edit_notice](/uploads/94ed1319404370ff1e9c0d672fb41e03/edit_notice.png)

## The user cannot change the target branch and is informed that editing will start an MR

![edit_footer](/uploads/4da68d4795c7177e575b7c434d16eeae/edit_footer.png)

## Hitting "Commit changes" will commit and start an MR from my fork to the origin project

![Screen_Shot_2015-12-17_at_23.38.08](/uploads/d777a4db6f38a5a1be84031694465bc1/Screen_Shot_2015-12-17_at_23.38.08.png)

-----

## "Create file, "Upload file" and "New directory" buttons in a project the user does NOT have write access to

![new_directory](/uploads/72f556248f30d6652523bbb4be01b3e0/new_directory.png)

## Clicking any of these options will automatically create a fork

![during_fork](/uploads/32f4f5dff9f24ea99522000b0bf881c5/during_fork.png)

## When the fork has been created, the user is returned to the tree page on the original project with a notice

![new_directory_notice](/uploads/a1a3e11308ae0e8f0913fae6813a37ed/new_directory_notice.png)

## Clicking "New directory" again will show the modal. The user cannot change the target branch and is informed that editing will start an MR

![new_dir](/uploads/99ca8cbfb2f70603e352b3fdf67b6281/new_dir.png)

## Hitting "Create directory" will commit and start an MR from my fork to the origin project

![Screen_Shot_2015-12-17_at_23.39.19](/uploads/3713d0235abf831361b803a6198c5bc1/Screen_Shot_2015-12-17_at_23.39.19.png)

cc @dzaporozhets @skyruler

See merge request !2145
parents 84e75ebd cfa716eb
...@@ -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]
......
...@@ -10,19 +10,35 @@ class Projects::ForksController < Projects::ApplicationController ...@@ -10,19 +10,35 @@ class Projects::ForksController < Projects::ApplicationController
def create def create
namespace = Namespace.find(params[:namespace_key]) namespace = Namespace.find(params[:namespace_key])
@forked_project = ::Projects::ForkService.new(project, current_user, namespace: namespace).execute
@forked_project = namespace.projects.find_by(path: project.path)
@forked_project = nil unless @forked_project && @forked_project.forked_from_project == project
@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?
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 else
redirect_to( if continue_params
namespace_project_path(@forked_project.namespace, @forked_project), redirect_to continue_params[:to], notice: continue_params[:notice]
notice: 'Project was successfully forked.' else
) redirect_to namespace_project_path(@forked_project.namespace, @forked_project), notice: "The project was successfully forked."
end
end end
else else
render :error render :error
end end
end end
private
def continue_params
continue_params = params[:continue]
if continue_params
continue_params.permit(:to, :notice, :notice_now)
else
nil
end
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,36 @@ class Projects::ImportsController < Projects::ApplicationController ...@@ -24,21 +24,36 @@ 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), notice: "The project was successfully forked."
@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]
if continue_params
continue_params.permit(:to, :notice, :notice_now)
else
nil
end
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_tree_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,90 @@ module BlobHelper ...@@ -22,32 +22,90 @@ 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) blob = project.repository.blob_at(ref, path) rescue nil
rescue
nil return unless blob && blob_text_viewable?(blob)
end
return unless blob && blob.text? && blob_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 if !on_top_of_branch?
) + after.html_safe 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 blob.lfs_pointer?
button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", 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 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
...@@ -70,7 +128,7 @@ module BlobHelper ...@@ -70,7 +128,7 @@ module BlobHelper
icon("#{file_type_icon_class('file', mode, name)} fw") icon("#{file_type_icon_class('file', mode, name)} fw")
end end
def blob_viewable?(blob) def blob_text_viewable?(blob)
blob && blob.text? && !blob.lfs_pointer? blob && blob.text? && !blob.lfs_pointer?
end end
......
...@@ -50,24 +50,49 @@ module TreeHelper ...@@ -50,24 +50,49 @@ 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)
ref if can_push_branch?(project, ref)
else ref
project.repository.next_patch_branch else
end project = tree_edit_project(project)
project.repository.next_patch_branch
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
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
def tree_breadcrumbs(tree, max_links = 2) def tree_breadcrumbs(tree, max_links = 2)
if @path.present? if @path.present?
part_path = "" part_path = ""
......
...@@ -592,47 +592,54 @@ class Repository ...@@ -592,47 +592,54 @@ class Repository
Gitlab::Popen.popen(args, path_to_repo) Gitlab::Popen.popen(args, path_to_repo)
end end
def commit_with_hooks(current_user, branch) def with_tmp_ref(oldrev = nil)
oldrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
was_empty = empty?
# Create temporary ref
random_string = SecureRandom.hex random_string = SecureRandom.hex
tmp_ref = "refs/tmp/#{random_string}/head" tmp_ref = "refs/tmp/#{random_string}/head"
unless was_empty if oldrev && !Gitlab::Git.blank_ref?(oldrev)
oldrev = find_branch(branch).target
rugged.references.create(tmp_ref, oldrev) rugged.references.create(tmp_ref, oldrev)
end end
# Make commit in tmp ref # Make commit in tmp ref
newrev = yield(tmp_ref) yield(tmp_ref)
ensure
rugged.references.delete(tmp_ref) rescue nil
end
def commit_with_hooks(current_user, branch)
oldrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
was_empty = empty?
unless newrev unless was_empty
raise CommitError.new('Failed to create commit') oldrev = find_branch(branch).target
end end
GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do with_tmp_ref(oldrev) do |tmp_ref|
if was_empty # Make commit in tmp ref
# Create branch newrev = yield(tmp_ref)
rugged.references.create(ref, newrev)
else unless newrev
# Update head raise CommitError.new('Failed to create commit')
current_head = find_branch(branch).target end
# Make sure target branch was not changed during pre-receive hook GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
if current_head == oldrev if was_empty
rugged.references.update(ref, newrev) # Create branch
rugged.references.create(ref, newrev)
else else
raise CommitError.new('Commit was rejected because branch received new push') # Update head
current_head = find_branch(branch).target
# Make sure target branch was not changed during pre-receive hook
if current_head == oldrev
rugged.references.update(ref, newrev)
else
raise CommitError.new('Commit was rejected because branch received new push')
end
end end
end end
end end
rescue GitHooksService::PreReceiveError
# 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 is invalid') return error('Branch name is 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 = repository.add_branch(current_user, branch_name, ref) 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)
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
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
= link_to 'Raw', namespace_project_raw_path(@project.namespace, @project, @id), = link_to 'Raw', namespace_project_raw_path(@project.namespace, @project, @id),
class: 'btn btn-sm', target: '_blank' class: 'btn btn-sm', target: '_blank'
-# only show normal/blame view links for text files -# only show normal/blame view links for text files
- if blob_viewable?(@blob) - if blob_text_viewable?(@blob)
- if current_page? namespace_project_blame_path(@project.namespace, @project, @id) - if current_page? namespace_project_blame_path(@project.namespace, @project, @id)
= link_to 'Normal View', namespace_project_blob_path(@project.namespace, @project, @id), = link_to 'Normal View', namespace_project_blob_path(@project.namespace, @project, @id),
class: 'btn btn-sm' class: 'btn btn-sm'
...@@ -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
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
= "#{diff_file.diff.a_mode}#{diff_file.diff.b_mode}" = "#{diff_file.diff.a_mode}#{diff_file.diff.b_mode}"
.diff-controls .diff-controls
- if blob_viewable?(blob) - if blob_text_viewable?(blob)
= link_to '#', class: 'js-toggle-diff-comments btn btn-sm active has_tooltip', title: "Toggle comments for this file" do = link_to '#', class: 'js-toggle-diff-comments btn btn-sm active has_tooltip', title: "Toggle comments for this file" do
%i.fa.fa-comments %i.fa.fa-comments
&nbsp; &nbsp;
...@@ -32,14 +32,15 @@ ...@@ -32,14 +32,15 @@
- 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)
.diff-content.diff-wrap-lines .diff-content.diff-wrap-lines
-# Skipp all non non-supported blobs -# Skipp all non non-supported blobs
- return unless blob.respond_to?('text?') - return unless blob.respond_to?('text?')
- if blob_viewable?(blob) - if blob_text_viewable?(blob)
- if diff_view == 'parallel' - if diff_view == 'parallel'
= render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- else - else
......
...@@ -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.
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
- if merge_request.open? && merge_request.broken? - if merge_request.open? && merge_request.broken?
%li %li
= link_to merge_request_path(merge_request), class: "has_tooltip", title: "Cannot be merged automatically", data: {container: 'body'} do = link_to merge_request_path(merge_request), class: "has_tooltip", title: "Cannot be merged automatically", data: { container: 'body' } do
= icon('exclamation-triangle') = icon('exclamation-triangle')
- if merge_request.assignee - if merge_request.assignee
......
...@@ -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,34 +11,65 @@ ...@@ -11,34 +11,65 @@
= 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
%span.dropdown - if !on_top_of_branch?
%a.dropdown-toggle.btn.btn-sm.add-to-tree{href: '#', "data-toggle" => "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' }}
= icon('plus') = icon('plus')
%ul.dropdown-menu - else
%li %span.dropdown
= link_to namespace_project_new_blob_path(@project.namespace, @project, @id), title: 'Create file', id: 'new-file-link' do %a.dropdown-toggle.btn.btn-sm.add-to-tree{href: '#', "data-toggle" => "dropdown"}
= icon('pencil fw') = icon('plus')
New file %ul.dropdown-menu
%li - if can_edit_tree?
= link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal'} do %li
= icon('file fw') = link_to namespace_project_new_blob_path(@project.namespace, @project, @id) do
Upload file = icon('pencil fw')
%li New file
= link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do %li
= icon('folder fw') = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal'} do
New directory = icon('file fw')
%li.divider Upload file
%li %li
= link_to new_namespace_project_branch_path(@project.namespace, @project) do = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do
= icon('code-fork fw') = icon('folder fw')
New branch New directory
%li - elsif can?(current_user, :fork_project, @project)
= link_to new_namespace_project_tag_path(@project.namespace, @project) do %li
= icon('tags fw') - continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @id),
New tag notice: edit_in_new_fork_notice,
- elsif !on_top_of_branch? notice_now: edit_in_new_fork_notice_now }
%li - fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
%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'}} continue: continue_params)
= icon('plus') = link_to fork_path, method: :post do
= icon('pencil fw')
New 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
- 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
%li.divider
%li
= link_to new_namespace_project_branch_path(@project.namespace, @project) do
= icon('code-fork fw')
New branch
%li
= link_to new_namespace_project_tag_path(@project.namespace, @project) do
= icon('tags fw')
New tag
= render 'shared/commit_message_container', placeholder: placeholder = render 'shared/commit_message_container', placeholder: placeholder
- unless @project.empty_repo? - if @project.empty_repo?
.form-group.branch = hidden_field_tag 'target_branch', @ref
= label_tag 'new_branch', 'Target branch', class: 'control-label' - else
.col-sm-10 - if can?(current_user, :push_code, @project)
= text_field_tag 'new_branch', @new_branch || tree_edit_branch, required: true, class: "form-control js-new-branch" .form-group.branch
= label_tag 'target_branch', 'Target branch', class: 'control-label'
.col-sm-10
= 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
- nonce = SecureRandom.hex - nonce = SecureRandom.hex
= 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'
...@@ -24,6 +24,12 @@ Feature: Project Source Browse Files ...@@ -24,6 +24,12 @@ Feature: Project Source Browse Files
Given I click on "New file" link in repo Given I click on "New file" link in repo
Then I can see new file page Then I can see new file page
Scenario: I can create file when I don't have write access
Given I don't have write access
And I click on "New file" link in repo
Then I should see a notice about a new fork having been created
Then I can see new file page
@javascript @javascript
Scenario: I can create and commit file Scenario: I can create and commit file
Given I click on "New file" link in repo Given I click on "New file" link in repo
...@@ -34,6 +40,17 @@ Feature: Project Source Browse Files ...@@ -34,6 +40,17 @@ Feature: Project Source Browse Files
Then I am redirected to the new file Then I am redirected to the new file
And I should see its new content And I should see its new content
@javascript
Scenario: I can create and commit file when I don't have write access
Given I don't have write access
And I click on "New file" link in repo
And I edit code
And I fill the new file name
And I fill the commit message
And I click on "Commit Changes"
Then I am redirected to the fork's new merge request page
And I can see the new commit message
@javascript @javascript
Scenario: I can create and commit file with new lines at the end of file Scenario: I can create and commit file with new lines at the end of file
Given I click on "New file" link in repo Given I click on "New file" link in repo
...@@ -45,6 +62,17 @@ Feature: Project Source Browse Files ...@@ -45,6 +62,17 @@ Feature: Project Source Browse Files
And I click button "Edit" And I click button "Edit"
And I should see its content with new lines preserved at end of file And I should see its content with new lines preserved at end of file
@javascript
Scenario: I can create and commit file and specify new branch
Given I click on "New file" link in repo
And I edit code
And I fill the new file name
And I fill the commit message
And I fill the new branch name
And I click on "Commit Changes"
Then I am redirected to the new merge request page
And I should see its new content
@javascript @javascript
Scenario: I can upload file and commit Scenario: I can upload file and commit
Given I click on "Upload file" link in repo Given I click on "Upload file" link in repo
...@@ -56,6 +84,19 @@ Feature: Project Source Browse Files ...@@ -56,6 +84,19 @@ Feature: Project Source Browse Files
And I am redirected to the new merge request page And I am redirected to the new merge request page
And I can see the new commit message And I can see the new commit message
@javascript
Scenario: I can upload file and commit when I don't have write access
Given I don't have write access
And I click on "Upload file" link in repo
Then I should see a notice about a new fork having been created
When I click on "Upload file" link in repo
And I upload a new text file
And I fill the upload file commit message
And I click on "Upload file"
Then I can see the new text file
And I am redirected to the fork's new merge request page
And I can see the new commit message
@javascript @javascript
Scenario: I can replace file and commit Scenario: I can replace file and commit
Given I click on ".gitignore" file in repo Given I click on ".gitignore" file in repo
...@@ -68,15 +109,19 @@ Feature: Project Source Browse Files ...@@ -68,15 +109,19 @@ Feature: Project Source Browse Files
And I can see the replacement commit message And I can see the replacement commit message
@javascript @javascript
Scenario: I can create and commit file and specify new branch Scenario: I can replace file and commit when I don't have write access
Given I click on "New file" link in repo Given I don't have write access
And I edit code And I click on ".gitignore" file in repo
And I fill the new file name And I see the ".gitignore"
And I fill the commit message And I click on "Replace"
And I fill the new branch name Then I should see a notice about a new fork having been created
And I click on "Commit Changes" When I click on "Replace"
Then I am redirected to the new merge request page And I replace it with a text file
And I should see its new content And I fill the replace file commit message
And I click on "Replace file"
Then I can see the new text file
And I am redirected to the fork's new merge request page
And I can see the replacement commit message
@javascript @javascript
Scenario: I can create file in empty repo Scenario: I can create file in empty repo
...@@ -117,6 +162,14 @@ Feature: Project Source Browse Files ...@@ -117,6 +162,14 @@ Feature: Project Source Browse Files
And I click button "Edit" And I click button "Edit"
Then I can edit code Then I can edit code
@javascript
Scenario: I can edit file when I don't have write access
Given I don't have write access
And I click on ".gitignore" file in repo
And I click button "Edit"
Then I should see a notice about a new fork having been created
And I can edit code
Scenario: If the file is binary the edit link is hidden Scenario: If the file is binary the edit link is hidden
Given I visit a binary file in the repo Given I visit a binary file in the repo
Then I cannot see the edit button Then I cannot see the edit button
...@@ -131,6 +184,17 @@ Feature: Project Source Browse Files ...@@ -131,6 +184,17 @@ Feature: Project Source Browse Files
Then I am redirected to the ".gitignore" Then I am redirected to the ".gitignore"
And I should see its new content And I should see its new content
@javascript
Scenario: I can edit and commit file when I don't have write access
Given I don't have write access
And I click on ".gitignore" file in repo
And I click button "Edit"
And I edit code
And I fill the commit message
And I click on "Commit Changes"
Then I am redirected to the fork's new merge request page
And I can see the new commit message
@javascript @javascript
Scenario: I can edit and commit file to new branch Scenario: I can edit and commit file to new branch
Given I click on ".gitignore" file in repo Given I click on ".gitignore" file in repo
...@@ -161,6 +225,17 @@ Feature: Project Source Browse Files ...@@ -161,6 +225,17 @@ Feature: Project Source Browse Files
And I click on "Create directory" And I click on "Create directory"
Then I am redirected to the new merge request page Then I am redirected to the new merge request page
@javascript
Scenario: I can create directory in repo when I don't have write access
Given I don't have write access
When I click on "New directory" link in repo
Then I should see a notice about a new fork having been created
When I click on "New directory" link in repo
And I fill the new directory name
And I fill the commit message
And I click on "Create directory"
Then I am redirected to the fork's new merge request page
@javascript @javascript
Scenario: I attempt to create an existing directory Scenario: I attempt to create an existing directory
When I click on "New directory" link in repo When I click on "New directory" link in repo
...@@ -188,6 +263,19 @@ Feature: Project Source Browse Files ...@@ -188,6 +263,19 @@ Feature: Project Source Browse Files
Then I am redirected to the files URL Then I am redirected to the files URL
And I don't see the ".gitignore" And I don't see the ".gitignore"
@javascript
Scenario: I can delete file and commit when I don't have write access
Given I don't have write access
And I click on ".gitignore" file in repo
And I see the ".gitignore"
And I click on "Delete"
Then I should see a notice about a new fork having been created
When I click on "Delete"
And I fill the commit message
And I click on "Delete file"
Then I am redirected to the fork's new merge request page
And I can see the new commit message
Scenario: I can browse directory with Browse Dir Scenario: I can browse directory with Browse Dir
Given I click on files directory Given I click on files directory
And I click on History link And I click on History link
......
...@@ -5,6 +5,12 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -5,6 +5,12 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
include SharedPaths include SharedPaths
include RepoHelpers include RepoHelpers
step "I don't have write access" do
@project = create(:project, name: "Other Project", path: "other-project")
@project.team << [@user, :reporter]
visit namespace_project_tree_path(@project.namespace, @project, root_ref)
end
step 'I should see files from repository' do step 'I should see files from repository' do
expect(page).to have_content "VERSION" expect(page).to have_content "VERSION"
expect(page).to have_content ".gitignore" expect(page).to have_content ".gitignore"
...@@ -75,7 +81,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -75,7 +81,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
...@@ -87,7 +93,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -87,7 +93,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end end
step 'I fill the commit message' do step 'I fill the commit message' do
fill_in :commit_message, with: 'Not yet a commit message.', visible: true fill_in :commit_message, with: 'New commit message', visible: true
end end
step 'I click link "Diff"' do step 'I click link "Diff"' do
...@@ -103,7 +109,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -103,7 +109,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end end
step 'I click on "Delete"' do step 'I click on "Delete"' do
click_button 'Delete' click_on 'Delete'
end end
step 'I click on "Delete file"' do step 'I click on "Delete file"' do
...@@ -111,7 +117,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -111,7 +117,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end end
step 'I click on "Replace"' do step 'I click on "Replace"' do
click_button "Replace" click_on "Replace"
end end
step 'I click on "Replace file"' do step 'I click on "Replace file"' do
...@@ -124,7 +130,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -124,7 +130,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step 'I click on "New file" link in repo' do step 'I click on "New file" link in repo' do
find('.add-to-tree').click find('.add-to-tree').click
click_link 'Create file' click_link 'New file'
end end
step 'I click on "Upload file" link in repo' do step 'I click on "Upload file" link in repo' do
...@@ -155,7 +161,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -155,7 +161,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end end
step 'I can see the new commit message' do step 'I can see the new commit message' do
expect(page).to have_content "New upload commit message" expect(page).to have_content "New commit message"
end end
step 'I upload a new text file' do step 'I upload a new text file' do
...@@ -164,7 +170,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -164,7 +170,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step 'I fill the upload file commit message' do step 'I fill the upload file commit message' do
page.within('#modal-upload-blob') do page.within('#modal-upload-blob') do
fill_in :commit_message, with: 'New upload commit message' fill_in :commit_message, with: 'New commit message'
end end
end end
...@@ -251,9 +257,14 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -251,9 +257,14 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
expect(current_path).to eq(new_namespace_project_merge_request_path(@project.namespace, @project)) expect(current_path).to eq(new_namespace_project_merge_request_path(@project.namespace, @project))
end end
step "I am redirected to the fork's new merge request page" do
fork = @user.fork_of(@project)
expect(current_path).to eq(new_namespace_project_merge_request_path(fork.namespace, fork))
end
step 'I am redirected to the root directory' do step 'I am redirected to the root directory' do
expect(current_path).to eq( expect(current_path).to eq(
namespace_project_tree_path(@project.namespace, @project, 'master/')) namespace_project_tree_path(@project.namespace, @project, 'master'))
end end
step "I don't see the permalink link" do step "I don't see the permalink link" do
...@@ -332,8 +343,12 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -332,8 +343,12 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
expect(page).to have_content 'Permalink' expect(page).to have_content 'Permalink'
expect(page).not_to have_content 'Edit' expect(page).not_to have_content 'Edit'
expect(page).not_to have_content 'Blame' expect(page).not_to have_content 'Blame'
expect(page).not_to have_content 'Delete' expect(page).to have_content 'Delete'
expect(page).not_to have_content 'Replace' expect(page).to have_content 'Replace'
end
step 'I should see a notice about a new fork having been created' do
expect(page).to have_content "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 end
private private
......
...@@ -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
...@@ -108,8 +108,8 @@ describe Projects::TreeController do ...@@ -108,8 +108,8 @@ 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}/tree/#{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
...@@ -119,7 +119,7 @@ describe Projects::TreeController do ...@@ -119,7 +119,7 @@ describe Projects::TreeController do
it 'does not allow overwriting of existing files' do it 'does not allow overwriting of existing files' do
expect(subject). expect(subject).
to redirect_to("/#{project.path_with_namespace}/blob/master") to redirect_to("/#{project.path_with_namespace}/tree/master")
expect(flash[:alert]).to eq('Directory already exists as a file') expect(flash[:alert]).to eq('Directory already exists as a file')
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