UserMergeToRef discards the underlying merge error, hiding conflict details from merge trains
Summary
When UserMergeToRef fails to produce a merge commit, it discards the
underlying error and replaces it with a generic message:
Failed to create merge commit for source_sha <sha> and target_sha <sha> at <ref>
This is the exact message GitLab surfaces to users when a merge request is
removed from a merge train (refs/merge-requests/<iid>/train), e.g.:
<user> removed this merge request from the merge train because 9:Failed to create merge commit for source_sha c8fc16dd and target_sha ad144da9 at refs/merge-requests/XXXX/train.
Users have no way to tell whether this was a real conflict, a race with the target branch, or something else - see gitlab#394901 and internal Slack thread: https://gitlab.slack.com/archives/CPCJ8CCCX/p1785367057331189 (originally raised in https://gitlab.slack.com/archives/C01EMBKS5DW/p1785366666689039).
Root cause
UserMergeToRef calls the same internal s.merge() helper as
UserMergeBranch, but handles its error very differently.
UserMergeBranch type-asserts the error and, for a *localrepo.MergeTreeConflictError,
returns a structured MergeConflictError detail (ConflictingFiles,
ConflictingCommitIds):
https://gitlab.com/gitlab-org/gitaly/-/blob/cc5fb3de1ff7feb57201284dec36be68f8d6d992/internal/gitaly/service/operations/merge_branch.go#L79-101
UserMergeToRef does neither - it only logs the real error server-side
(s.logger.WithError(err)...) and returns a generic FailedPrecondition
with no message and no structured detail, so the caller (GitLab/Gitaly
client) cannot distinguish a genuine conflict from any other merge failure.
There is also no UserMergeToRefError proto message (unlike
UserMergeBranchError), so there's currently nowhere to attach structured
detail even if the error were inspected.
Proposal
- Mirror
UserMergeBranch's handling inUserMergeToRef: useerrors.Asto detect*localrepo.MergeTreeConflictErrorand return the conflicting file/commit information to the caller instead of swallowing it. - Add a
UserMergeToRefErrorproto message (following theUserMergeBranchErrorpattern) so aMergeConflictErrordetail can be attached. - This would let GitLab (
ee/app/services/system_notes/merge_train_service.rb) present the actual conflict reason when a merge request is dropped from a merge train, resolving gitlab#394901 at the root cause.