Commit c9c2503c authored by Katarzyna Kobierska's avatar Katarzyna Kobierska

User can edit closed MR with deleted fork

Add test for closed MR without fork

Add view test visibility of Reopen and Close buttons

Fix controller tests and validation method

Fix missing space

Remove unused variables from test

closed_without_fork? method refactoring

Add information about missing fork

When closed MR without fork can't edit target branch

Tests for closed MR edit view

Fix indentation and rebase, refactoring
parent 2778dec1
...@@ -223,6 +223,7 @@ v 8.10.6 ...@@ -223,6 +223,7 @@ v 8.10.6
- Restore "Largest repository" sort option on Admin > Projects page. !5797 - Restore "Largest repository" sort option on Admin > Projects page. !5797
- Fix privilege escalation via project export. - Fix privilege escalation via project export.
- Require administrator privileges to perform a project import. - Require administrator privileges to perform a project import.
- User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496
v 8.10.5 v 8.10.5
- Add a data migration to fix some missing timestamps in the members table. !5670 - Add a data migration to fix some missing timestamps in the members table. !5670
......
...@@ -98,6 +98,6 @@ module MergeRequestsHelper ...@@ -98,6 +98,6 @@ module MergeRequestsHelper
end end
def merge_request_button_visibility(merge_request, closed) def merge_request_button_visibility(merge_request, closed)
return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork?
end end
end end
...@@ -91,13 +91,13 @@ class MergeRequest < ActiveRecord::Base ...@@ -91,13 +91,13 @@ class MergeRequest < ActiveRecord::Base
end end
end end
validates :source_project, presence: true, unless: [:allow_broken, :importing?] validates :source_project, presence: true, unless: [:allow_broken, :importing?, :closed_without_fork?]
validates :source_branch, presence: true validates :source_branch, presence: true
validates :target_project, presence: true validates :target_project, presence: true
validates :target_branch, presence: true validates :target_branch, presence: true
validates :merge_user, presence: true, if: :merge_when_build_succeeds? validates :merge_user, presence: true, if: :merge_when_build_succeeds?
validate :validate_branches, unless: [:allow_broken, :importing?] validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?]
validate :validate_fork validate :validate_fork, unless: :closed_without_fork?
scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) } scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) } scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
...@@ -305,19 +305,22 @@ class MergeRequest < ActiveRecord::Base ...@@ -305,19 +305,22 @@ class MergeRequest < ActiveRecord::Base
def validate_fork def validate_fork
return true unless target_project && source_project return true unless target_project && source_project
return true if target_project == source_project
return true unless fork_missing?
if target_project == source_project errors.add :validate_fork,
true 'Source project is not a fork of target project'
else end
# If source and target projects are different
# we should check if source project is actually a fork of target project def closed_without_fork?
if source_project.forked_from?(target_project) closed? && fork_missing?
true end
else
errors.add :validate_fork, def fork_missing?
'Source project is not a fork of target project' return false unless for_fork?
end return true unless source_project
end
!source_project.forked_from?(target_project)
end end
def ensure_merge_request_diff def ensure_merge_request_diff
......
...@@ -11,6 +11,10 @@ module MergeRequests ...@@ -11,6 +11,10 @@ module MergeRequests
params.except!(:target_project_id) params.except!(:target_project_id)
params.except!(:source_branch) params.except!(:source_branch)
if merge_request.closed_without_fork?
params.except!(:target_branch, :force_remove_source_branch)
end
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
update(merge_request) update(merge_request)
......
- if @merge_request.closed_without_fork?
.alert.alert-danger
%p Source project is not a fork of the target project
.clearfix.detail-page-header .clearfix.detail-page-header
.issuable-header .issuable-header
.issuable-status-box.status-box{ class: status_box_class(@merge_request) } .issuable-status-box.status-box{ class: status_box_class(@merge_request) }
......
...@@ -135,28 +135,29 @@ ...@@ -135,28 +135,29 @@
= icon('question-circle') = icon('question-circle')
- if issuable.is_a?(MergeRequest) - if issuable.is_a?(MergeRequest)
%hr - unless @merge_request.closed_without_fork?
- if @merge_request.new_record? %hr
- if @merge_request.new_record?
.form-group
= f.label :source_branch, class: 'control-label'
.col-sm-10
.issuable-form-select-holder
= f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true })
.form-group .form-group
= f.label :source_branch, class: 'control-label' = f.label :target_branch, class: 'control-label'
.col-sm-10 .col-sm-10
.issuable-form-select-holder .issuable-form-select-holder
= f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true }) = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record?, data: {placeholder: "Select branch"} })
.form-group - if @merge_request.new_record?
= f.label :target_branch, class: 'control-label' &nbsp;
.col-sm-10 = link_to 'Change branches', mr_change_branches_path(@merge_request)
.issuable-form-select-holder - if @merge_request.can_remove_source_branch?(current_user)
= f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record?, data: {placeholder: "Select branch"} }) .form-group
- if @merge_request.new_record? .col-sm-10.col-sm-offset-2
&nbsp; .checkbox
= link_to 'Change branches', mr_change_branches_path(@merge_request) = label_tag 'merge_request[force_remove_source_branch]' do
- if @merge_request.can_remove_source_branch?(current_user) = check_box_tag 'merge_request[force_remove_source_branch]', '1', @merge_request.force_remove_source_branch?
.form-group Remove source branch when merge request is accepted.
.col-sm-10.col-sm-offset-2
.checkbox
= label_tag 'merge_request[force_remove_source_branch]' do
= check_box_tag 'merge_request[force_remove_source_branch]', '1', @merge_request.force_remove_source_branch?
Remove source branch when merge request is accepted.
- is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?) - is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?)
.row-content-block{class: (is_footer ? "footer-block" : "middle-block")} .row-content-block{class: (is_footer ? "footer-block" : "middle-block")}
...@@ -175,7 +176,7 @@ ...@@ -175,7 +176,7 @@
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]), class: 'btn btn-cancel' = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]), class: 'btn btn-cancel'
- else - else
.pull-right .pull-right
- if current_user.can?(:"destroy_#{issuable.to_ability_name}", @project) - if can?(current_user, :"destroy_#{issuable.to_ability_name}", @project)
= link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" }, = link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" },
method: :delete, class: 'btn btn-danger btn-grouped' method: :delete, class: 'btn btn-danger btn-grouped'
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel' = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel'
......
...@@ -170,6 +170,35 @@ describe Projects::MergeRequestsController do ...@@ -170,6 +170,35 @@ describe Projects::MergeRequestsController do
expect(response).to redirect_to([merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request]) expect(response).to redirect_to([merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request])
expect(merge_request.reload.closed?).to be_truthy expect(merge_request.reload.closed?).to be_truthy
end end
it 'allow to edit closed MR' do
merge_request.close!
put :update,
namespace_id: project.namespace.path,
project_id: project.path,
id: merge_request.iid,
merge_request: {
title: 'New title'
}
expect(response).to redirect_to([merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request])
expect(merge_request.reload.title).to eq 'New title'
end
it 'does not allow to update target branch closed MR' do
merge_request.close!
put :update,
namespace_id: project.namespace.path,
project_id: project.path,
id: merge_request.iid,
merge_request: {
target_branch: 'new_branch'
}
expect { merge_request.reload.target_branch }.not_to change { merge_request.target_branch }
end
end end
end end
......
...@@ -962,4 +962,68 @@ describe MergeRequest, models: true do ...@@ -962,4 +962,68 @@ describe MergeRequest, models: true do
expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_truthy expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_truthy
end end
end end
describe "#fork_missing?" do
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
let(:user) { create(:user) }
let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) }
context "when fork exists" do
let(:merge_request) do
create(:merge_request,
source_project: fork_project,
target_project: project)
end
it { expect(merge_request.fork_missing?).to be_falsey }
end
context "when source project is the target project" do
let(:merge_request) { create(:merge_request, source_project: project) }
it { expect(merge_request.fork_missing?).to be_falsey }
end
context "when fork does not exist" do
let(:merge_request) do
create(:merge_request,
source_project: fork_project,
target_project: project)
end
it do
unlink_project.execute
merge_request.reload
expect(merge_request.fork_missing?).to be_truthy
end
end
end
describe "#closed_without_fork?" do
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
let(:user) { create(:user) }
let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) }
context "closed MR" do
let(:closed_merge_request) do
create(:closed_merge_request,
source_project: fork_project,
target_project: project)
end
it "has a fork" do
expect(closed_merge_request.closed_without_fork?).to be_falsey
end
it "does not have a fork" do
unlink_project.execute
closed_merge_request.reload
expect(closed_merge_request.closed_without_fork?).to be_truthy
end
end
end
end end
require 'spec_helper'
describe 'projects/merge_requests/edit.html.haml' do
include Devise::TestHelpers
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
let(:closed_merge_request) do
create(:closed_merge_request,
source_project: fork_project,
target_project: project,
author: user)
end
let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) }
before do
assign(:project, project)
assign(:merge_request, closed_merge_request)
allow(view).to receive(:can?).and_return(true)
allow(view).to receive(:current_user).and_return(User.find(closed_merge_request.author_id))
end
context 'when closed MR without fork' do
it "shows editable fields" do
unlink_project.execute
closed_merge_request.reload
render
expect(rendered).to have_field('merge_request[title]')
expect(rendered).to have_css('label', text: "Title")
expect(rendered).to have_field('merge_request[description]')
expect(rendered).to have_css('label', text: "Description")
expect(rendered).to have_css('label', text: "Assignee")
expect(rendered).to have_css('label', text: "Milestone")
expect(rendered).to have_css('label', text: "Labels")
expect(rendered).not_to have_css('label', text: "Target branch")
end
end
end
require 'spec_helper'
describe 'projects/merge_requests/show.html.haml' do
include Devise::TestHelpers
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
let(:merge_request) do
create(:merge_request,
source_project: fork_project,
source_branch: 'add-submodule-version-bump',
target_branch: 'master', target_project: project)
end
before do
assign(:project, project)
assign(:merge_request, merge_request)
assign(:commits_count, 0)
merge_request.close!
allow(view).to receive(:can?).and_return(true)
end
context 'closed MR' do
it 'shows Reopen button' do
render
expect(rendered).to have_css('a', visible: true, text: 'Reopen')
expect(rendered).to have_css('a', visible: false, text: 'Close')
end
it 'does not show Reopen button without fork' do
fork_project.destroy
render
expect(rendered).to have_css('a', visible: false, text: 'Reopen')
expect(rendered).to have_css('a', visible: false, text: 'Close')
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