fix: Follow file renames on the commits page
ListCommits.follow support
Why
#628900 (closed) describes a bug where on the commits page we stopped following file renames. This was caused by refactor that moved the page off the REST/FindCommits path onto a GraphQL query resolved by Resolvers::Repositories::CommitsResolver, which calls Repository#list_commits and thus Gitaly's ListCommits RPC, which had no rename-following.
What
Make the commits page follow file renames again. Threads a follow: keyword down the existing chain (CommitService#list_commits -> Gitlab::Git::Repository#list_commits -> Repository#list_commits) and opts in from CommitsResolver only for the safe case.
fix details
thread a follow: keyword down the existing chain and opts in from CommitsResolver only for the safe case:
- a single scalar path
- the new
list_commits_followfeature flag enabled for the project - the existing
remove_file_commit_history_followingops flag OFF
reverse stays hardcoded false and skip is never forwarded on this path, because gitaly rejects follow combined with reverse, multiple paths, or skip.
Feature flag
New gitlab_com_derisk flag list_commits_follow, default_enabled: false, milestone 19.5.
Completes the Rails half of #595517 (closed) (page path).
LLM friendly Local validation steps
Click to expand
MR: !256649 (merged)
What it does: The commits page (file-history view) now follows file renames.
A follow: flag is threaded from the GraphQL commits resolver down to the Gitaly
ListCommits RPC, gated behind the new list_commits_follow feature flag.
These steps let a reviewer confirm the behavior against a local GDK. They cover
the main paths only — the edge cases (guardrails against follow+reverse/skip,
per-layer defaulting, the ops-flag override, pagination internals) are already
covered by the specs added in this MR, so there's no need to reproduce them by hand.
Prerequisites
- A running GDK checked out on this MR's branch.
- Gitaly 19.4+. The
followsupport ships with the Gitaly 19.4 gem bump (the first commit on this branch). If your GDK's Gitaly is older, thefollowfield is silently ignored and the "after" results below will look like the "before" results.gdk update(or rebuilding Gitaly) ensures you're current. - Test fixtures — already present in every GDK seed, nothing to create:
- Project:
gitlab-org/gitlab-test - Branch:
blame-on-renamed - Renamed path:
files/plain_text/renamed
- Project:
Feature flag
| Flag | Type | Default | Restart needed after flip? |
|---|---|---|---|
list_commits_follow |
gitlab_com_derisk (Ruby, per-request) |
OFF | No — read per request; the next query sees the new value |
Flip it from the Rails console (gdk rails console / bin/rails console):
# Enable
Feature.enable(:list_commits_follow)
# Disable (restore default)
Feature.disable(:list_commits_follow)There is also an existing kill-switch, remove_file_commit_history_following (ops
flag, default OFF). Leave it at its default for these steps — its override behavior
is covered by specs.
Option A — Rails console (fewest moving parts)
Everything below runs in gdk rails console. This exercises the same
Repository#list_commits(follow:) path the GraphQL resolver uses.
project = Project.find_by_full_path('gitlab-org/gitlab-test')
repo = project.repository
ref = 'blame-on-renamed'
path = 'files/plain_text/renamed'
# Baseline — history dead-ends at the rename
literal = repo.list_commits(ref: ref, path: path, follow: false).commits.map(&:id)
literal.size
# => 2 (the bug: pre-rename history is missing)
# The fix — following the rename adds the pre-rename commits
followed = repo.list_commits(ref: ref, path: path, follow: true).commits.map(&:id)
followed.size
# => 4
followed - literal # the 2 pre-rename commits that were previously missing
followed & literal == literal # => true (followed is a strict superset of literal)Expected: follow: false → 2 commits; follow: true → 4 commits, containing all
of the literal 2 plus 2 pre-rename commits. That difference is the entire fix.
To confirm the feature-flag gating end-to-end (resolver level), toggle the flag and re-run the GraphQL query in Option B.
Option B — GraphQL (the real commits-page path)
Use the GraphiQL explorer at /-/graphql-explorer on your GDK, or any GraphQL client
authenticated to your GDK. This is the exact query the commits page issues.
Step 1 — Baseline: flag OFF reproduces the bug
Ensure the flag is OFF (default), then run:
{
project(fullPath: "gitlab-org/gitlab-test") {
repository {
commits(ref: "blame-on-renamed", path: "files/plain_text/renamed", first: 100) {
nodes { sha }
}
}
}
}Expected: exactly 2 nodes (post-rename history only) — the bug this MR fixes.
Step 2 — Enable the flag
In the Rails console: Feature.enable(:list_commits_follow). No restart required.
Step 3 — Flag ON follows the rename (the fix)
Re-run the identical query from Step 1.
Expected: 4 nodes — the 2 from Step 1 plus 2 pre-rename commits that were previously missing. This is the fix working end-to-end through the real commits-page resolver.
Step 4 — Sanity check: no path still works
With the flag still ON, query a branch with no path:
{
project(fullPath: "gitlab-org/gitlab-test") {
repository {
commits(ref: "blame-on-renamed", first: 3) {
nodes { sha }
pageInfo { hasNextPage }
}
}
}
}Expected: normal branch history (3 nodes, hasNextPage: true), no errors in the
response — confirming follow is not requested when there's no path.
Optional — confirm the UI
Enable the flag, then open the commits page for the renamed file in the browser:
/gitlab-org/gitlab-test/-/commits/blame-on-renamed/files/plain_text/renamedExpected: with the flag ON, the list now shows the pre-rename commits (the history continues past the rename); with it OFF, it stops at the rename.
Cleanup — restore the flag
In the Rails console:
Feature.disable(:list_commits_follow)Re-run the Step 1 query (or Option A baseline) to confirm you're back to 2 commits.
Summary of expected results
| Scenario | Flag | Path | Expected |
|---|---|---|---|
| Baseline | OFF | renamed path | 2 commits (bug: dead-ends at rename) |
| The fix | ON | renamed path | 4 commits (follows rename) |
| No path | ON | none | normal history, no error |
Edge cases (per-layer defaulting, follow+reverse/skip guardrails, the
remove_file_commit_history_following override, pagination cursor internals) are
covered by the specs added in this MR and don't need manual reproduction.
Before merging
- Merge !256643 (merged) first
- Retarget this MR to
master - Confirm feature/Capybara behaviour in CI/QA
- Run the full pipeline (un-draft)