Merge branch '21636-deleting-source-project-with-existing-fork-closes-all-related-merge-requests' into 'master'
Deleting source project with existing fork link should closes all related merge requests, and does not render `invalid` template for merge requests with deleted source project. The merge request without source project can be edited but can't be reopened. Closes #21636 See merge request !6177
This commit is contained in:
commit
404f438f45
8 changed files with 127 additions and 21 deletions
|
@ -127,6 +127,7 @@ v 8.12.0 (unreleased)
|
||||||
- Allow bulk update merge requests from merge requests index page
|
- Allow bulk update merge requests from merge requests index page
|
||||||
- Add notification_settings API calls !5632 (mahcsig)
|
- Add notification_settings API calls !5632 (mahcsig)
|
||||||
- Remove duplication between project builds and admin builds view !5680 (Katarzyna Kobierska Ula Budziszewska)
|
- Remove duplication between project builds and admin builds view !5680 (Katarzyna Kobierska Ula Budziszewska)
|
||||||
|
- Deleting source project with existing fork link will close all related merge requests !6177 (Katarzyna Kobierska Ula Budziszeska)
|
||||||
|
|
||||||
v 8.11.6 (unreleased)
|
v 8.11.6 (unreleased)
|
||||||
- Fix an error where we were unable to create a CommitStatus for running state
|
- Fix an error where we were unable to create a CommitStatus for running state
|
||||||
|
|
|
@ -428,17 +428,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def validates_merge_request
|
def validates_merge_request
|
||||||
# If source project was removed (Ex. mr from fork to origin)
|
|
||||||
return invalid_mr unless @merge_request.source_project
|
|
||||||
|
|
||||||
# Show git not found page
|
# Show git not found page
|
||||||
# if there is no saved commits between source & target branch
|
# if there is no saved commits between source & target branch
|
||||||
if @merge_request.commits.blank?
|
if @merge_request.commits.blank?
|
||||||
# and if target branch doesn't exist
|
# and if target branch doesn't exist
|
||||||
return invalid_mr unless @merge_request.target_branch_exists?
|
return invalid_mr unless @merge_request.target_branch_exists?
|
||||||
|
|
||||||
# or if source branch doesn't exist
|
|
||||||
return invalid_mr unless @merge_request.source_branch_exists?
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -316,6 +316,10 @@ class MergeRequest < ActiveRecord::Base
|
||||||
closed? && forked_source_project_missing?
|
closed? && forked_source_project_missing?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def closed_without_source_project?
|
||||||
|
closed? && !source_project
|
||||||
|
end
|
||||||
|
|
||||||
def forked_source_project_missing?
|
def forked_source_project_missing?
|
||||||
return false unless for_fork?
|
return false unless for_fork?
|
||||||
return true unless source_project
|
return true unless source_project
|
||||||
|
@ -323,6 +327,12 @@ class MergeRequest < ActiveRecord::Base
|
||||||
!source_project.forked_from?(target_project)
|
!source_project.forked_from?(target_project)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reopenable?
|
||||||
|
return false if closed_without_fork? || closed_without_source_project? || merged?
|
||||||
|
|
||||||
|
closed?
|
||||||
|
end
|
||||||
|
|
||||||
def ensure_merge_request_diff
|
def ensure_merge_request_diff
|
||||||
merge_request_diff || create_merge_request_diff
|
merge_request_diff || create_merge_request_diff
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,6 +27,8 @@ module Projects
|
||||||
# Git data (e.g. a list of branch names).
|
# Git data (e.g. a list of branch names).
|
||||||
flush_caches(project, wiki_path)
|
flush_caches(project, wiki_path)
|
||||||
|
|
||||||
|
Projects::UnlinkForkService.new(project, current_user).execute
|
||||||
|
|
||||||
Project.transaction do
|
Project.transaction do
|
||||||
project.destroy!
|
project.destroy!
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
- if can?(current_user, :update_merge_request, @merge_request)
|
- if can?(current_user, :update_merge_request, @merge_request)
|
||||||
- if @merge_request.open?
|
- if @merge_request.open?
|
||||||
= link_to 'Close merge request', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-comment btn-close close-mr-link js-note-target-close", title: "Close merge request", data: {original_text: "Close merge request", alternative_text: "Comment & close merge request"}
|
= link_to 'Close merge request', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-comment btn-close close-mr-link js-note-target-close", title: "Close merge request", data: {original_text: "Close merge request", alternative_text: "Comment & close merge request"}
|
||||||
- if @merge_request.closed?
|
- if @merge_request.reopenable?
|
||||||
= link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request", data: {original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"}
|
= link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request", data: {original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"}
|
||||||
%comment-and-resolve-btn{ "inline-template" => true, ":discussion-id" => "" }
|
%comment-and-resolve-btn{ "inline-template" => true, ":discussion-id" => "" }
|
||||||
%button.btn.btn-nr.btn-default.append-right-10.js-comment-resolve-button{ "v-if" => "showButton", type: "submit", data: { namespace_path: "#{@merge_request.project.namespace.path}", project_path: "#{@merge_request.project.path}" } }
|
%button.btn.btn-nr.btn-default.append-right-10.js-comment-resolve-button{ "v-if" => "showButton", type: "submit", data: { namespace_path: "#{@merge_request.project.namespace.path}", project_path: "#{@merge_request.project.path}" } }
|
||||||
|
|
|
@ -29,17 +29,19 @@
|
||||||
%ul.dropdown-menu.dropdown-menu-align-right
|
%ul.dropdown-menu.dropdown-menu-align-right
|
||||||
%li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
|
%li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
|
||||||
%li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
|
%li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
|
||||||
.normal
|
- unless @merge_request.closed_without_fork?
|
||||||
%span Request to merge
|
.normal
|
||||||
%span.label-branch= source_branch_with_namespace(@merge_request)
|
%span Request to merge
|
||||||
%span into
|
%span.label-branch= source_branch_with_namespace(@merge_request)
|
||||||
%span.label-branch
|
%span into
|
||||||
= link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch)
|
%span.label-branch
|
||||||
- if @merge_request.open? && @merge_request.diverged_from_target_branch?
|
= link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch)
|
||||||
%span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind)
|
- if @merge_request.open? && @merge_request.diverged_from_target_branch?
|
||||||
|
%span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind)
|
||||||
|
|
||||||
= render "projects/merge_requests/show/how_to_merge"
|
- unless @merge_request.closed_without_source_project?
|
||||||
= render "projects/merge_requests/widget/show.html.haml"
|
= render "projects/merge_requests/show/how_to_merge"
|
||||||
|
= render "projects/merge_requests/widget/show.html.haml"
|
||||||
|
|
||||||
- if @merge_request.source_branch_exists? && @merge_request.mergeable? && @merge_request.can_be_merged_by?(current_user)
|
- if @merge_request.source_branch_exists? && @merge_request.mergeable? && @merge_request.can_be_merged_by?(current_user)
|
||||||
.light.prepend-top-default.append-bottom-default
|
.light.prepend-top-default.append-bottom-default
|
||||||
|
@ -53,10 +55,11 @@
|
||||||
= link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do
|
= link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do
|
||||||
Discussion
|
Discussion
|
||||||
%span.badge= @merge_request.mr_and_commit_notes.user.count
|
%span.badge= @merge_request.mr_and_commit_notes.user.count
|
||||||
%li.commits-tab
|
- unless @merge_request.closed_without_source_project?
|
||||||
= link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do
|
%li.commits-tab
|
||||||
Commits
|
= link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do
|
||||||
%span.badge= @commits_count
|
Commits
|
||||||
|
%span.badge= @commits_count
|
||||||
- if @pipeline
|
- if @pipeline
|
||||||
%li.pipelines-tab
|
%li.pipelines-tab
|
||||||
= link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do
|
= link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do
|
||||||
|
|
|
@ -181,6 +181,25 @@ describe ProjectsController do
|
||||||
expect(response).to have_http_status(302)
|
expect(response).to have_http_status(302)
|
||||||
expect(response).to redirect_to(dashboard_projects_path)
|
expect(response).to redirect_to(dashboard_projects_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "when the project is forked" do
|
||||||
|
let(:project) { create(:project) }
|
||||||
|
let(:fork_project) { create(:project, forked_from_project: project) }
|
||||||
|
let(:merge_request) do
|
||||||
|
create(:merge_request,
|
||||||
|
source_project: fork_project,
|
||||||
|
target_project: project)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "closes all related merge requests" do
|
||||||
|
project.merge_requests << merge_request
|
||||||
|
sign_in(admin)
|
||||||
|
|
||||||
|
delete :destroy, namespace_id: fork_project.namespace.path, id: fork_project.path
|
||||||
|
|
||||||
|
expect(merge_request.reload.state).to eq('closed')
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "POST #toggle_star" do
|
describe "POST #toggle_star" do
|
||||||
|
|
|
@ -1038,4 +1038,81 @@ describe MergeRequest, models: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#closed_without_source_project?' do
|
||||||
|
let(:project) { create(:project) }
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
let(:fork_project) { create(:project, forked_from_project: project, namespace: user.namespace) }
|
||||||
|
let(:destroy_service) { Projects::DestroyService.new(fork_project, user) }
|
||||||
|
|
||||||
|
context 'when the merge request is closed' do
|
||||||
|
let(:closed_merge_request) do
|
||||||
|
create(:closed_merge_request,
|
||||||
|
source_project: fork_project,
|
||||||
|
target_project: project)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false if the source project exists' do
|
||||||
|
expect(closed_merge_request.closed_without_source_project?).to be_falsey
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true if the source project does not exist' do
|
||||||
|
destroy_service.execute
|
||||||
|
closed_merge_request.reload
|
||||||
|
|
||||||
|
expect(closed_merge_request.closed_without_source_project?).to be_truthy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the merge request is open' do
|
||||||
|
it 'returns false' do
|
||||||
|
expect(subject.closed_without_source_project?).to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#reopenable?' do
|
||||||
|
context 'when the merge request is closed' do
|
||||||
|
it 'returns true' do
|
||||||
|
subject.close
|
||||||
|
|
||||||
|
expect(subject.reopenable?).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'forked project' do
|
||||||
|
let(:project) { create(:project) }
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
let(:fork_project) { create(:project, forked_from_project: project, namespace: user.namespace) }
|
||||||
|
let(:merge_request) do
|
||||||
|
create(:closed_merge_request,
|
||||||
|
source_project: fork_project,
|
||||||
|
target_project: project)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false if unforked' do
|
||||||
|
Projects::UnlinkForkService.new(fork_project, user).execute
|
||||||
|
|
||||||
|
expect(merge_request.reload.reopenable?).to be_falsey
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false if the source project is deleted' do
|
||||||
|
Projects::DestroyService.new(fork_project, user).execute
|
||||||
|
|
||||||
|
expect(merge_request.reload.reopenable?).to be_falsey
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false if the merge request is merged' do
|
||||||
|
merge_request.update_attributes(state: 'merged')
|
||||||
|
|
||||||
|
expect(merge_request.reload.reopenable?).to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the merge request is opened' do
|
||||||
|
it 'returns false' do
|
||||||
|
expect(subject.reopenable?).to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue