Remove ?ref_type=heads in Gitlab file/dir link to reduce verbosity

Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.

Maybe since mid Aug 2023, Gitlab links to file or directory now always has a query string parameter ?ref_type=heads. Example:

https://<myproject_path>/-/blob/master/.gitlab-ci.yml?ref_type=heads

We often paste the Gitlab URL in our Slack/Teams messages and internal documentation. The ?ref_type=heads parameter is just noise adding no value to the link.

Gitlab support had indicated that the ?ref_type=heads was added to address issue 20526 Impossible to browse a branch if a tag exists with the same name

Would it be possible to have a user/group config somewhere to disable the ?ref_type=heads workaround? As it never occurred to us to give an identical name to a branch and a tag. Also confirmed by a comment on that same issue 20526 that it might not be a good idea:

using branches and tags of the same name isn't a pattern that works well. If you do the same thing locally, you'll end up with warnings like warning: refname 'v0.0.1' is ambiguous

On github, there is no equivalent of ?ref_type=heads.

Proposal

Only include ref_type in a URL when the ref is genuinely ambiguous — i.e. a branch and a tag share the same name, or the name is itself symbolic (e.g. refs/heads/...). For the ~99% of refs that are unambiguous, links stay clean (/-/blob/master/file.rb). This implements the conditional ref_type approach described in this comment.

Goal

ref_type is present in a URL only when required to disambiguate. Unambiguous refs produce no ?ref_type= query parameter in repository navigation, while the frontend still knows which type of ref is being viewed.

Why we can avoid forcing ref_type into the URL

  • Unambiguous refs: ExtractsRef::RefExtractor#ref_type returns nil when no ref_type param is supplied, and Rails omits nil query params, so link helpers that pass ref_type: @ref_type already emit nothing for the common case.
  • Ambiguity is computed once per request, not per link: set_is_ambiguous_ref (app/controllers/projects/application_controller.rb) short-circuits to false when @ref_type is present, and otherwise calls ExtractsRef::VerifiedRefExtractor#ambiguous_ref? a single time for the ref being viewed.

Scope of changes

1. Provide the ref type to the frontend without putting it in the URL

The ref selector dropdown (app/assets/javascripts/ref/components/ref_selector.vue) currently derives the branch/tag icon (dropdownIcon) from a symbolic ref string, which is only available when ref_type is present in the URL (via useSymbolicRefNames). Removing ref_type from the URL would therefore break the icon for unambiguous refs.

To fix this we need a system for initializing @ref_type even when @is_ambiguous_ref is false, so the frontend always knows the type of ref being viewed regardless of whether ref_type is in the URL.

ExtractsPath#ref_type (lib/extracts_path.rb) already resolves the type via VerifiedRefExtractor.ref_type, which detects heads/tags for unambiguous refs and returns nil only when the ref is ambiguous. The work is to:

  • Ensure the blob/tree views that mount the ref selector expose this detected @ref_type to the frontend as data, separately from the URL query param.
  • Update ref_selector.vue so the icon (and any other ref-type-dependent display) is driven by this passed-in value rather than by parsing a symbolic ref out of the URL — e.g. matching on ref_type.present? ? [ref_type, ref_name] : ref_name.

Tree, blob, blame, and find_file views propagate @ref_type through their navigation links. Because @ref_type is nil in the URL-param sense for unambiguous refs, these links are clean as long as we do not force the detected type into the URL. This item is verification plus removing any literal unconditional ref_type: from these links, keeping the detected type available to the frontend (item 1) without serializing it into the URL.

3. Ambiguous-ref entry prompt

When a user navigates to an ambiguous ref without a ref_type (e.g. a shared clean URL that happens to be ambiguous), the existing ambiguous-ref modal (app/assets/javascripts/ref/init_ambiguous_ref_modal.js) asks the user to choose branch or tag and sets ref_type. This flow remains the single point where ref_type is (re-)introduced into the URL. Confirm the modal is mounted on all relevant views (tree, blob, commits, project show).

Implementation note: caching ambiguity / ref-type lookups

Detecting the ref type for an unambiguous ref still relies on VerifiedRefExtractor (tag_exists? / branch_exists?), which are Gitaly calls. Resolving @ref_type for the frontend on views that previously relied on the URL param may therefore add lookups on those requests. We have not confirmed the performance impact. The implementing engineer should evaluate whether these lookups need caching (e.g. memoization per request, or a cheaper repo-level ambiguity check) before rollout, and measure rather than assume.

Out of scope

  • List pages (branches, tags): these render a single, known ref type per page, so passing ref_type: :heads / :tags there is correct and intentional — not the verbose-URL problem this issue targets. No change.
  • Putting fully-qualified refs (refs/heads/...) directly in URL paths — rejected because RefExtractor#extract_ref re-splits the URL id against short ref names and would mis-parse a qualified path.

Acceptance criteria

  • Navigating to / sharing an unambiguous ref produces no ?ref_type= in tree, blob, blame, find_file, raw, download, and ref-switcher URLs.
  • The ref-selector dropdown shows the correct branch/tag icon for the current ref without relying on ref_type in the URL.
  • Ambiguous refs continue to resolve to the correct content (branch vs tag).
  • The ambiguous-ref modal still appears when navigating to an ambiguous ref without ref_type, and is the mechanism that sets ref_type.
  • Any added ref-type lookups for unambiguous refs have been assessed for performance, with caching applied if measurement shows it is needed.
Edited by Joe Woodward