Commit 1bdfc561 authored by Kerri Miller's avatar Kerri Miller

Merge branch 'suggestions-use-target-instead-of-source-project' into 'master'

Suggestions: use template from target project instead of source project

See merge request gitlab-org/gitlab!82657
parents 1bb4f2b9 1c64a7a6
......@@ -16,10 +16,14 @@ class Suggestion < ApplicationRecord
note.latest_diff_file
end
def project
def source_project
noteable.source_project
end
def target_project
noteable.target_project
end
def branch
noteable.source_branch
end
......
# frozen_string_literal: true
class SuggestionPolicy < BasePolicy
delegate { @subject.project }
delegate { @subject.source_project }
condition(:can_push_to_branch) do
Gitlab::UserAccess.new(@user, container: @subject.project).can_push_to_branch?(@subject.branch)
Gitlab::UserAccess.new(@user, container: @subject.source_project).can_push_to_branch?(@subject.branch)
end
rule { can_push_to_branch }.enable :apply_suggestion
......
......@@ -54,7 +54,7 @@ module Suggestions
author_email: author&.email
}
::Files::MultiService.new(suggestion_set.project, current_user, params)
::Files::MultiService.new(suggestion_set.source_project, current_user, params)
end
def commit_message
......
......@@ -108,6 +108,8 @@ For example, to customize the commit message to output
**Addresses user_1's review**, set the custom text to
`Addresses %{username}'s review`.
For merge requests created from forks, GitLab uses the template defined in target project.
NOTE:
Custom commit messages for each applied suggestion is
introduced by [#25381](https://gitlab.com/gitlab-org/gitlab/-/issues/25381).
......
......@@ -13,7 +13,7 @@ module Gitlab
end
def message
project = suggestion_set.project
project = suggestion_set.target_project
user_defined_message = @custom_message.presence || project.suggestion_commit_message.presence
message = user_defined_message || DEFAULT_SUGGESTION_COMMIT_MESSAGE
......@@ -37,8 +37,8 @@ module Gitlab
'branch_name' => ->(user, suggestion_set) { suggestion_set.branch },
'files_count' => ->(user, suggestion_set) { suggestion_set.file_paths.length },
'file_paths' => ->(user, suggestion_set) { format_paths(suggestion_set.file_paths) },
'project_name' => ->(user, suggestion_set) { suggestion_set.project.name },
'project_path' => ->(user, suggestion_set) { suggestion_set.project.path },
'project_name' => ->(user, suggestion_set) { suggestion_set.target_project.name },
'project_path' => ->(user, suggestion_set) { suggestion_set.target_project.path },
'user_full_name' => ->(user, suggestion_set) { user.name },
'username' => ->(user, suggestion_set) { user.username },
'suggestions_count' => ->(user, suggestion_set) { suggestion_set.suggestions.size }
......
......@@ -9,8 +9,12 @@ module Gitlab
@suggestions = suggestions
end
def project
first_suggestion.project
def source_project
first_suggestion.source_project
end
def target_project
first_suggestion.target_project
end
def branch
......
......@@ -3,7 +3,10 @@
require 'spec_helper'
RSpec.describe Gitlab::Suggestions::CommitMessage do
def create_suggestion(file_path, new_line, to_content)
include ProjectForksHelper
using RSpec::Parameterized::TableSyntax
def create_suggestion(merge_request, file_path, new_line, to_content)
position = Gitlab::Diff::Position.new(old_path: file_path,
new_path: file_path,
old_line: nil,
......@@ -29,69 +32,111 @@ RSpec.describe Gitlab::Suggestions::CommitMessage do
create(:project, :repository, path: 'project-1', name: 'Project_1')
end
let_it_be(:merge_request) do
let_it_be(:forked_project) { fork_project(project, nil, repository: true) }
let_it_be(:merge_request_same_project) do
create(:merge_request, source_project: project, target_project: project)
end
let_it_be(:suggestion_set) do
suggestion1 = create_suggestion('files/ruby/popen.rb', 9, '*** SUGGESTION 1 ***')
suggestion2 = create_suggestion('files/ruby/popen.rb', 13, '*** SUGGESTION 2 ***')
suggestion3 = create_suggestion('files/ruby/regex.rb', 22, '*** SUGGESTION 3 ***')
let_it_be(:merge_request_from_fork) do
create(:merge_request, source_project: forked_project, target_project: project)
end
let_it_be(:suggestion_set_same_project) do
suggestion1 = create_suggestion(merge_request_same_project, 'files/ruby/popen.rb', 9, '*** SUGGESTION 1 ***')
suggestion2 = create_suggestion(merge_request_same_project, 'files/ruby/popen.rb', 13, '*** SUGGESTION 2 ***')
suggestion3 = create_suggestion(merge_request_same_project, 'files/ruby/regex.rb', 22, '*** SUGGESTION 3 ***')
Gitlab::Suggestions::SuggestionSet.new([suggestion1, suggestion2, suggestion3])
end
let_it_be(:suggestion_set_forked_project) do
suggestion1 = create_suggestion(merge_request_from_fork, 'files/ruby/popen.rb', 9, '*** SUGGESTION 1 ***')
suggestion2 = create_suggestion(merge_request_from_fork, 'files/ruby/popen.rb', 13, '*** SUGGESTION 2 ***')
suggestion3 = create_suggestion(merge_request_from_fork, 'files/ruby/regex.rb', 22, '*** SUGGESTION 3 ***')
Gitlab::Suggestions::SuggestionSet.new([suggestion1, suggestion2, suggestion3])
end
describe '#message' do
before do
# Updating the suggestion_commit_message on a project shared across specs
# avoids recreating the repository for each spec.
project.update!(suggestion_commit_message: message)
end
where(:suggestion_set) { [ref(:suggestion_set_same_project), ref(:suggestion_set_forked_project)] }
with_them do
before do
# Updating the suggestion_commit_message on a project shared across specs
# avoids recreating the repository for each spec.
project.update!(suggestion_commit_message: message)
forked_project.update!(suggestion_commit_message: fork_message)
end
let(:fork_message) { nil }
context 'when a custom commit message is not specified' do
let(:expected_message) { 'Apply 3 suggestion(s) to 2 file(s)' }
context 'when a custom commit message is not specified' do
let(:expected_message) { 'Apply 3 suggestion(s) to 2 file(s)' }
context 'and is nil' do
let(:message) { nil }
context 'and is nil' do
let(:message) { nil }
it 'uses the default commit message' do
expect(described_class
.new(user, suggestion_set)
.message).to eq(expected_message)
it 'uses the default commit message' do
expect(described_class
.new(user, suggestion_set)
.message).to eq(expected_message)
end
end
end
context 'and is an empty string' do
let(:message) { '' }
context 'and is an empty string' do
let(:message) { '' }
it 'uses the default commit message' do
expect(described_class
.new(user, suggestion_set)
.message).to eq(expected_message)
it 'uses the default commit message' do
expect(described_class
.new(user, suggestion_set)
.message).to eq(expected_message)
end
end
end
end
context 'when a custom commit message is specified' do
let(:message) { "i'm a project message. a user's custom message takes precedence over me :(" }
let(:custom_message) { "hello there! i'm a cool custom commit message." }
context 'when a custom commit message is specified for forked project' do
let(:message) { nil }
let(:fork_message) { "I'm a sad message that will not be used :(" }
it 'shows the custom commit message' do
expect(Gitlab::Suggestions::CommitMessage
.new(user, suggestion_set, custom_message)
.message).to eq(custom_message)
it 'uses the default commit message' do
expect(described_class
.new(user, suggestion_set)
.message).to eq(expected_message)
end
end
end
end
context 'is specified and includes all placeholders' do
let(:message) do
'*** %{branch_name} %{files_count} %{file_paths} %{project_name} %{project_path} %{user_full_name} %{username} %{suggestions_count} ***'
context 'when a custom commit message is specified' do
let(:message) { "i'm a project message. a user's custom message takes precedence over me :(" }
let(:custom_message) { "hello there! i'm a cool custom commit message." }
it 'shows the custom commit message' do
expect(Gitlab::Suggestions::CommitMessage
.new(user, suggestion_set, custom_message)
.message).to eq(custom_message)
end
end
it 'generates a custom commit message' do
expect(Gitlab::Suggestions::CommitMessage
.new(user, suggestion_set)
.message).to eq('*** master 2 files/ruby/popen.rb, files/ruby/regex.rb Project_1 project-1 Test User test.user 3 ***')
context 'is specified and includes all placeholders' do
let(:message) do
'*** %{branch_name} %{files_count} %{file_paths} %{project_name} %{project_path} %{user_full_name} %{username} %{suggestions_count} ***'
end
it 'generates a custom commit message' do
expect(Gitlab::Suggestions::CommitMessage
.new(user, suggestion_set)
.message).to eq('*** master 2 files/ruby/popen.rb, files/ruby/regex.rb Project_1 project-1 Test User test.user 3 ***')
end
context 'when a custom commit message is specified for forked project' do
let(:fork_message) { "I'm a sad message that will not be used :(" }
it 'uses the target project commit message' do
expect(Gitlab::Suggestions::CommitMessage
.new(user, suggestion_set)
.message).to eq('*** master 2 files/ruby/popen.rb, files/ruby/regex.rb Project_1 project-1 Test User test.user 3 ***')
end
end
end
end
end
......
......@@ -3,6 +3,9 @@
require 'spec_helper'
RSpec.describe Gitlab::Suggestions::SuggestionSet do
include ProjectForksHelper
using RSpec::Parameterized::TableSyntax
def create_suggestion(file_path, new_line, to_content)
position = Gitlab::Diff::Position.new(old_path: file_path,
new_path: file_path,
......@@ -24,86 +27,99 @@ RSpec.describe Gitlab::Suggestions::SuggestionSet do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:forked_project) { fork_project(project, nil, repository: true) }
let_it_be(:merge_request) do
let_it_be(:merge_request_same_project) do
create(:merge_request, source_project: project, target_project: project)
end
let_it_be(:suggestion) { create(:suggestion)}
let_it_be(:suggestion2) do
create_suggestion('files/ruby/popen.rb', 13, "*** SUGGESTION 2 ***")
end
let_it_be(:suggestion3) do
create_suggestion('files/ruby/regex.rb', 22, "*** SUGGESTION 3 ***")
let_it_be(:merge_request_from_fork) do
create(:merge_request, source_project: forked_project, target_project: project)
end
let_it_be(:unappliable_suggestion) { create(:suggestion, :unappliable) }
where(:merge_request) { [ref(:merge_request_same_project), ref(:merge_request_from_fork)] }
with_them do
let(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request) }
let(:suggestion) { create(:suggestion, note: note) }
let(:suggestion_set) { described_class.new([suggestion]) }
describe '#project' do
it 'returns the project associated with the suggestions' do
expected_project = suggestion.project
let(:suggestion2) do
create_suggestion('files/ruby/popen.rb', 13, "*** SUGGESTION 2 ***")
end
expect(suggestion_set.project).to be(expected_project)
let(:suggestion3) do
create_suggestion('files/ruby/regex.rb', 22, "*** SUGGESTION 3 ***")
end
end
describe '#branch' do
it 'returns the branch associated with the suggestions' do
expected_branch = suggestion.branch
let(:unappliable_suggestion) { create(:suggestion, :unappliable) }
let(:suggestion_set) { described_class.new([suggestion]) }
expect(suggestion_set.branch).to be(expected_branch)
describe '#source_project' do
it 'returns the source project associated with the suggestions' do
expect(suggestion_set.source_project).to be(merge_request.source_project)
end
end
end
describe '#valid?' do
it 'returns true if no errors are found' do
expect(suggestion_set.valid?).to be(true)
describe '#target_project' do
it 'returns the target project associated with the suggestions' do
expect(suggestion_set.target_project).to be(project)
end
end
it 'returns false if an error is found' do
suggestion_set = described_class.new([unappliable_suggestion])
describe '#branch' do
it 'returns the branch associated with the suggestions' do
expected_branch = suggestion.branch
expect(suggestion_set.valid?).to be(false)
expect(suggestion_set.branch).to be(expected_branch)
end
end
end
describe '#error_message' do
it 'returns an error message if an error is found' do
suggestion_set = described_class.new([unappliable_suggestion])
describe '#valid?' do
it 'returns true if no errors are found' do
expect(suggestion_set.valid?).to be(true)
end
expect(suggestion_set.error_message).to be_a(String)
it 'returns false if an error is found' do
suggestion_set = described_class.new([unappliable_suggestion])
expect(suggestion_set.valid?).to be(false)
end
end
it 'returns nil if no errors are found' do
expect(suggestion_set.error_message).to be(nil)
describe '#error_message' do
it 'returns an error message if an error is found' do
suggestion_set = described_class.new([unappliable_suggestion])
expect(suggestion_set.error_message).to be_a(String)
end
it 'returns nil if no errors are found' do
expect(suggestion_set.error_message).to be(nil)
end
end
end
describe '#actions' do
it 'returns an array of hashes with proper key/value pairs' do
first_action = suggestion_set.actions.first
describe '#actions' do
it 'returns an array of hashes with proper key/value pairs' do
first_action = suggestion_set.actions.first
file_suggestion = suggestion_set.send(:suggestions_per_file).first
file_suggestion = suggestion_set.send(:suggestions_per_file).first
expect(first_action[:action]).to be('update')
expect(first_action[:file_path]).to eq(file_suggestion.file_path)
expect(first_action[:content]).to eq(file_suggestion.new_content)
expect(first_action[:action]).to be('update')
expect(first_action[:file_path]).to eq(file_suggestion.file_path)
expect(first_action[:content]).to eq(file_suggestion.new_content)
end
end
end
describe '#file_paths' do
it 'returns an array of unique file paths associated with the suggestions' do
suggestion_set = described_class.new([suggestion, suggestion2, suggestion3])
describe '#file_paths' do
it 'returns an array of unique file paths associated with the suggestions' do
suggestion_set = described_class.new([suggestion, suggestion2, suggestion3])
expected_paths = %w(files/ruby/popen.rb files/ruby/regex.rb)
expected_paths = %w(files/ruby/popen.rb files/ruby/regex.rb)
actual_paths = suggestion_set.file_paths
actual_paths = suggestion_set.file_paths
expect(actual_paths.sort).to eq(expected_paths)
expect(actual_paths.sort).to eq(expected_paths)
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