Commit 88a8286a authored by Stan Hu's avatar Stan Hu

Improve error handling of squash and rebase

Previously all Git-related exceptions in squash and rebase would quietly
go to `git_json.log`, but these messages should really be tracked in
Sentry and/or `exceptions_json.log`.

In addition, if the squash in progress check failed, the merge would get
stuck. This commit now handles that error gracefully and logs it.

In the future, we can likely remove `git_json.log` altogether.
parent 3614deb7
# frozen_string_literal: true
module Git
module Logger
def log_error(message, save_message_on_model: false)
Gitlab::GitLogger.error("#{self.class.name} error (#{merge_request.to_reference(full: true)}): #{message}")
merge_request.update(merge_error: message) if save_message_on_model
end
end
end
...@@ -114,6 +114,32 @@ module MergeRequests ...@@ -114,6 +114,32 @@ module MergeRequests
yield merge_request yield merge_request
end end
end end
def log_error(exception:, message:, save_message_on_model: false)
reference = merge_request.to_reference(full: true)
data = {
class: self.class.name,
message: message,
merge_request_id: merge_request.id,
merge_request: reference,
save_message_on_model: save_message_on_model
}
if exception
Gitlab::ErrorTracking.with_context(current_user) do
Gitlab::ErrorTracking.track_exception(exception, data)
end
data[:"exception.message"] = exception.message
end
# TODO: Deprecate Gitlab::GitLogger since ErrorTracking should suffice:
# https://gitlab.com/gitlab-org/gitlab/-/issues/216379
data[:message] = "#{self.class.name} error (#{reference}): #{message}"
Gitlab::GitLogger.error(data)
merge_request.update(merge_error: message) if save_message_on_model
end
end end
end end
......
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
module MergeRequests module MergeRequests
class RebaseService < MergeRequests::BaseService class RebaseService < MergeRequests::BaseService
include Git::Logger
REBASE_ERROR = 'Rebase failed. Please rebase locally' REBASE_ERROR = 'Rebase failed. Please rebase locally'
attr_reader :merge_request attr_reader :merge_request
...@@ -22,7 +20,7 @@ module MergeRequests ...@@ -22,7 +20,7 @@ module MergeRequests
def rebase def rebase
# Ensure Gitaly isn't already running a rebase # Ensure Gitaly isn't already running a rebase
if source_project.repository.rebase_in_progress?(merge_request.id) if source_project.repository.rebase_in_progress?(merge_request.id)
log_error('Rebase task canceled: Another rebase is already in progress', save_message_on_model: true) log_error(exception: nil, message: 'Rebase task canceled: Another rebase is already in progress', save_message_on_model: true)
return false return false
end end
...@@ -30,8 +28,8 @@ module MergeRequests ...@@ -30,8 +28,8 @@ module MergeRequests
true true
rescue => e rescue => e
log_error(REBASE_ERROR, save_message_on_model: true) log_error(exception: e, message: REBASE_ERROR, save_message_on_model: true)
log_error(e.message)
false false
ensure ensure
merge_request.update_column(:rebase_jid, nil) merge_request.update_column(:rebase_jid, nil)
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
module MergeRequests module MergeRequests
class SquashService < MergeRequests::BaseService class SquashService < MergeRequests::BaseService
include Git::Logger SquashInProgressError = Class.new(RuntimeError)
def execute def execute
# If performing a squash would result in no change, then # If performing a squash would result in no change, then
...@@ -11,11 +11,13 @@ module MergeRequests ...@@ -11,11 +11,13 @@ module MergeRequests
return success(squash_sha: merge_request.diff_head_sha) return success(squash_sha: merge_request.diff_head_sha)
end end
if merge_request.squash_in_progress? if squash_in_progress?
return error(s_('MergeRequests|Squash task canceled: another squash is already in progress.')) return error(s_('MergeRequests|Squash task canceled: another squash is already in progress.'))
end end
squash! || error(s_('MergeRequests|Failed to squash. Should be done manually.')) squash! || error(s_('MergeRequests|Failed to squash. Should be done manually.'))
rescue SquashInProgressError
error(s_('MergeRequests|An error occurred while checking whether another squash is in progress.'))
end end
private private
...@@ -25,11 +27,19 @@ module MergeRequests ...@@ -25,11 +27,19 @@ module MergeRequests
success(squash_sha: squash_sha) success(squash_sha: squash_sha)
rescue => e rescue => e
log_error("Failed to squash merge request #{merge_request.to_reference(full: true)}:") log_error(exception: e, message: 'Failed to squash merge request')
log_error(e.message)
false false
end end
def squash_in_progress?
merge_request.squash_in_progress?
rescue => e
log_error(exception: e, message: 'Failed to check squash in progress')
raise SquashInProgressError, e.message
end
def repository def repository
target_project.repository target_project.repository
end end
......
---
title: Improve error handling of squash and rebase
merge_request: 23740
author:
type: other
...@@ -13051,6 +13051,9 @@ msgstr "" ...@@ -13051,6 +13051,9 @@ msgstr ""
msgid "MergeRequests|Add a reply" msgid "MergeRequests|Add a reply"
msgstr "" msgstr ""
msgid "MergeRequests|An error occurred while checking whether another squash is in progress."
msgstr ""
msgid "MergeRequests|An error occurred while saving the draft comment." msgid "MergeRequests|An error occurred while saving the draft comment."
msgstr "" msgstr ""
......
...@@ -72,12 +72,15 @@ describe MergeRequests::RebaseService do ...@@ -72,12 +72,15 @@ describe MergeRequests::RebaseService do
it_behaves_like 'sequence of failure and success' it_behaves_like 'sequence of failure and success'
context 'when unexpected error occurs' do context 'when unexpected error occurs' do
let(:exception) { RuntimeError.new('Something went wrong') }
let(:merge_request_ref) { merge_request.to_reference(full: true) }
before do before do
allow(repository).to receive(:gitaly_operation_client).and_raise('Something went wrong') allow(repository).to receive(:gitaly_operation_client).and_raise(exception)
end end
it 'saves a generic error message' do it 'saves a generic error message' do
subject.execute(merge_request) service.execute(merge_request)
expect(merge_request.reload.merge_error).to eq(described_class::REBASE_ERROR) expect(merge_request.reload.merge_error).to eq(described_class::REBASE_ERROR)
end end
...@@ -86,6 +89,18 @@ describe MergeRequests::RebaseService do ...@@ -86,6 +89,18 @@ describe MergeRequests::RebaseService do
expect(service.execute(merge_request)).to match(status: :error, expect(service.execute(merge_request)).to match(status: :error,
message: described_class::REBASE_ERROR) message: described_class::REBASE_ERROR)
end end
it 'logs the error' do
expect(service).to receive(:log_error).with(exception: exception, message: described_class::REBASE_ERROR, save_message_on_model: true).and_call_original
expect(Gitlab::ErrorTracking).to receive(:track_exception).with(exception,
class: described_class.to_s,
merge_request: merge_request_ref,
merge_request_id: merge_request.id,
message: described_class::REBASE_ERROR,
save_message_on_model: true).and_call_original
service.execute(merge_request)
end
end end
context 'with git command failure' do context 'with git command failure' do
......
...@@ -141,15 +141,14 @@ describe MergeRequests::SquashService do ...@@ -141,15 +141,14 @@ describe MergeRequests::SquashService do
let(:merge_request) { merge_request_with_only_new_files } let(:merge_request) { merge_request_with_only_new_files }
let(:error) { 'A test error' } let(:error) { 'A test error' }
context 'with gitaly enabled' do context 'with an error in Gitaly UserSquash RPC' do
before do before do
allow(repository.gitaly_operation_client).to receive(:user_squash) allow(repository.gitaly_operation_client).to receive(:user_squash)
.and_raise(Gitlab::Git::Repository::GitError, error) .and_raise(Gitlab::Git::Repository::GitError, error)
end end
it 'logs the stage and output' do it 'logs the error' do
expect(service).to receive(:log_error).with(log_error) expect(service).to receive(:log_error).with(exception: an_instance_of(Gitlab::Git::Repository::GitError), message: 'Failed to squash merge request')
expect(service).to receive(:log_error).with(error)
service.execute service.execute
end end
...@@ -158,19 +157,42 @@ describe MergeRequests::SquashService do ...@@ -158,19 +157,42 @@ describe MergeRequests::SquashService do
expect(service.execute).to match(status: :error, message: a_string_including('squash')) expect(service.execute).to match(status: :error, message: a_string_including('squash'))
end end
end end
context 'with an error in squash in progress check' do
before do
allow(repository).to receive(:squash_in_progress?)
.and_raise(Gitlab::Git::Repository::GitError, error)
end
it 'logs the stage and output' do
expect(service).to receive(:log_error).with(exception: an_instance_of(Gitlab::Git::Repository::GitError), message: 'Failed to check squash in progress')
service.execute
end
it 'returns an error' do
expect(service.execute).to match(status: :error, message: 'An error occurred while checking whether another squash is in progress.')
end
end
end end
context 'when any other exception is thrown' do context 'when any other exception is thrown' do
let(:merge_request) { merge_request_with_only_new_files } let(:merge_request) { merge_request_with_only_new_files }
let(:error) { 'A test error' } let(:merge_request_ref) { merge_request.to_reference(full: true) }
let(:exception) { RuntimeError.new('A test error') }
before do before do
allow(merge_request.target_project.repository).to receive(:squash).and_raise(error) allow(merge_request.target_project.repository).to receive(:squash).and_raise(exception)
end end
it 'logs the MR reference and exception' do it 'logs the error' do
expect(service).to receive(:log_error).with(a_string_including("#{project.full_path}#{merge_request.to_reference}")) expect(service).to receive(:log_error).with(exception: exception, message: 'Failed to squash merge request').and_call_original
expect(service).to receive(:log_error).with(error) expect(Gitlab::ErrorTracking).to receive(:track_exception).with(exception,
class: described_class.to_s,
merge_request: merge_request_ref,
merge_request_id: merge_request.id,
message: 'Failed to squash merge request',
save_message_on_model: false).and_call_original
service.execute service.execute
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