Exporting Merge Requests by CSV ignores some filters
Summary
CSV export of merge requests returns significantly more results than displayed in the UI when using filters on the "merged" tab (specifically merged_by, target_branch, and deployed_after). Seemingly some filters are ignored when exporting the CSV and it results in a mismatch between the amount being filtered and the amount actually in the CSV.
Steps to reproduce
- Navigate to a project's merge requests page
- Apply a mix of the following filters on the "merged" tab:
- Sort:
created_asc - State:
merged - Merged by: specific user (e.g.,
cleveland) - Target branch:
main - Deployed after:
2025-01-01
- Sort:
- Note the count displayed in the UI (e.g., 933 entries)
- Click the three-vertical-dot menu and select "Export as CSV"
- Check the received CSV file
Example URL:
https://gitlab.example.com/project/-/merge_requests/?sort=created_asc&state=merged&merge_user=cleveland&target_branches%5B%5D=main&deployed_after=2025-01-01&first_page_size=20
Expected behavior
The CSV export should contain the same number of merge requests as displayed in the UI.
Actual behavior
The CSV export contains significantly more entries than shown in the UI (2000+ entries in the example, more than double the expected count).
Relevant logs and/or screenshots
Note in my example, I have two of my own users to test with. In the UI, I am filtering by one user and should have 3 MRs, but the CSV contains 4, one of which is my other user which was not filtered out.
Environment
- GitLab version: Reported in GitLab SM 18.2.1 (replicated on GitLab.com)
Root Cause Analysis (Duo Assisted)
The issue stems from three filters in MergeRequestsFinder that don't properly respect the state parameter constraint when joining related tables:
1. by_merge_user filter (MergeUserFilter)
- Joins to
merge_request_metricstable without re-applying state constraint - Returns MRs with matching metrics regardless of actual merge state
2. by_merged_at filter (MergedAtFilter)
- Similar issue with
merge_request_metricsjoin - Doesn't verify state after applying date filters
3. by_deployments filter
- Joins to
deployment_merge_requestswithout state verification - Can return non-merged MRs if deployment records exist
Code references:
- https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/finders/concerns/merge_user_filter.rb
- https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/finders/concerns/merged_at_filter.rb
- https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/finders/merge_requests_finder.rb#L265-L283
- https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/services/merge_requests/export_csv_service.rb
Possible fixes
The filters should re-apply the state constraint after joining to related tables, or the join scopes should include state verification in their WHERE clauses.
Workaround
Use GraphQL API to query merge requests with proper filtering:
query {
project(fullPath: "group/project") {
mergeRequests(
state: merged
mergedBy: "username"
deployedAfter: "2025-01-01T00:00:00Z"
first: 100
) {
pageInfo {
hasNextPage
endCursor
}
edges {
node {
id
iid
title
state
targetBranch
sourceBranch
mergeUser {
username
}
createdAt
mergedAt
}
}
}
}
}

