Commit 864870c6 authored by Sean McGivern's avatar Sean McGivern

Don't reset approvals after rebase from UI

Rebasing from the UI should be considered a 'safe' action, so this
should ignore the 'reset approvals on push' project
setting.

Unfortunately, as rebase is implemented by working directly on
a copy of the repository and pushing (as it's not supported fully by
libgit2), we can't detect this purely with information available to the
PostReceive job. If we used commit metadata, the MR author could also
add the same metadata and push without resetting approvals.

To work around this, add a new column to the MergeRequest model to store
the SHA from the rebase action. (Ensure that this is set before pushing,
to avoid a race condition!) Then, in PostReceive, don't reset approvals
if the pushed SHA matches the SHA stored in the database.
parent 36866fbe
...@@ -13,6 +13,7 @@ v 8.9.0 (unreleased) ...@@ -13,6 +13,7 @@ v 8.9.0 (unreleased)
- [Elastic] Move ES settings to application settings - [Elastic] Move ES settings to application settings
- Disable mirror flag for projects without import_url - Disable mirror flag for projects without import_url
- UpdateMirror service return an error status when no mirror - UpdateMirror service return an error status when no mirror
- Don't reset approvals when rebasing an MR from the UI
- Show flash notice when Git Hooks are updated successfully - Show flash notice when Git Hooks are updated successfully
- Remove explicit Gitlab::Metrics.action assignments, are already automatic. - Remove explicit Gitlab::Metrics.action assignments, are already automatic.
- [Elastic] Project members with guest role can't access confidential issues - [Elastic] Project members with guest role can't access confidential issues
......
...@@ -52,6 +52,19 @@ module MergeRequests ...@@ -52,6 +52,19 @@ module MergeRequests
return false return false
end end
output, status = popen(
%W(git rev-parse #{merge_request.source_branch}),
tree_path,
git_env
)
unless status.zero?
log('Failed to get SHA of rebased branch:')
log(output)
return false
end
merge_request.update_attributes(rebase_commit_sha: output.chomp)
# Push # Push
output, status = popen( output, status = popen(
%W(git push -f origin #{merge_request.source_branch}), %W(git push -f origin #{merge_request.source_branch}),
......
...@@ -83,7 +83,10 @@ module MergeRequests ...@@ -83,7 +83,10 @@ module MergeRequests
merge_requests_for_source_branch.each do |merge_request| merge_requests_for_source_branch.each do |merge_request|
target_project = merge_request.target_project target_project = merge_request.target_project
if target_project.approvals_before_merge.nonzero? && target_project.reset_approvals_on_push if target_project.approvals_before_merge.nonzero? &&
target_project.reset_approvals_on_push &&
merge_request.rebase_commit_sha != @newrev
merge_request.approvals.destroy_all merge_request.approvals.destroy_all
end end
end end
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddRebaseCommitShaToMergeRequests < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# When using the methods "add_concurrent_index" or "add_column_with_default"
# you must disable the use of transactions as these methods can not run in an
# existing transaction. When using "add_concurrent_index" make sure that this
# method is the _only_ method called in the migration, any other changes
# should go in a separate migration. This ensures that upon failure _only_ the
# index creation fails and can be retried or reverted easily.
#
# To disable transactions uncomment the following line and remove these
# comments:
# disable_ddl_transaction!
def change
add_column :merge_requests, :rebase_commit_sha, :string
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160616084004) do ActiveRecord::Schema.define(version: 20160621123729) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -708,6 +708,7 @@ ActiveRecord::Schema.define(version: 20160616084004) do ...@@ -708,6 +708,7 @@ ActiveRecord::Schema.define(version: 20160616084004) do
t.string "merge_commit_sha" t.string "merge_commit_sha"
t.datetime "deleted_at" t.datetime "deleted_at"
t.integer "approvals_before_merge" t.integer "approvals_before_merge"
t.string "rebase_commit_sha"
end end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
......
...@@ -26,6 +26,11 @@ describe MergeRequests::RebaseService do ...@@ -26,6 +26,11 @@ describe MergeRequests::RebaseService do
target_branch_sha = merge_request.target_project.repository.commit(merge_request.target_branch).sha target_branch_sha = merge_request.target_project.repository.commit(merge_request.target_branch).sha
expect(parent_sha).to eq(target_branch_sha) expect(parent_sha).to eq(target_branch_sha)
end end
it 'records the new SHA on the merge request' do
head_sha = merge_request.source_project.repository.commit(merge_request.source_branch).sha
expect(merge_request.reload.rebase_commit_sha).to eq(head_sha)
end
end end
end end
end end
...@@ -173,27 +173,62 @@ describe MergeRequests::RefreshService, services: true do ...@@ -173,27 +173,62 @@ describe MergeRequests::RefreshService, services: true do
end end
context 'resetting approvals if they are enabled' do context 'resetting approvals if they are enabled' do
it "does not reset approvals if approvals_before_merge is disabled" do context 'when approvals_before_merge is disabled' do
before do
@project.update(approvals_before_merge: 0) @project.update(approvals_before_merge: 0)
refresh_service = service.new(@project, @user) refresh_service = service.new(@project, @user)
allow(refresh_service).to receive(:execute_hooks) allow(refresh_service).to receive(:execute_hooks)
refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
reload_mrs reload_mrs
end
it 'does not reset approvals' do
expect(@merge_request.approvals).not_to be_empty expect(@merge_request.approvals).not_to be_empty
end end
end
it "does not reset approvals if reset_approvals_on_push is disabled" do context 'when approvals_before_merge is disabled' do
before do
@project.update(reset_approvals_on_push: false) @project.update(reset_approvals_on_push: false)
refresh_service = service.new(@project, @user) refresh_service = service.new(@project, @user)
allow(refresh_service).to receive(:execute_hooks) allow(refresh_service).to receive(:execute_hooks)
refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
reload_mrs reload_mrs
end
it 'does not reset approvals' do
expect(@merge_request.approvals).not_to be_empty expect(@merge_request.approvals).not_to be_empty
end end
end end
context 'when the rebase_commit_sha on the MR matches the pushed SHA' do
before do
@merge_request.update(rebase_commit_sha: @newrev)
refresh_service = service.new(@project, @user)
allow(refresh_service).to receive(:execute_hooks)
refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
reload_mrs
end
it 'does not reset approvals' do
expect(@merge_request.approvals).not_to be_empty
end
end
context 'when there are approvals to be reset' do
before do
refresh_service = service.new(@project, @user)
allow(refresh_service).to receive(:execute_hooks)
refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
reload_mrs
end
it 'resets the approvals' do
expect(@merge_request.approvals).to be_empty
end
end
end
context 'push new branch that exists in a merge request' do context 'push new branch that exists in a merge request' do
let(:refresh_service) { service.new(@fork_project, @user) } let(:refresh_service) { service.new(@fork_project, @user) }
......
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