Handle dangling reference error from RefExists

What does this MR do and why?

When a repository has a dangling reference (a ref that exists but whose target object is missing, such as a refs/keep-around/<sha> pointing at a garbage-collected commit), RefExists surfaces an opaque Gitlab::Git::CommandError. This breaks repository mirroring and requires a manual console fix. Gitaly gitlab-org/gitaly!8967 now returns a typed error for this case, but Rails must consume it.

This MR:

  • Adds Gitlab::Git::ReferenceUnresolvableError carrying the offending reference name and oid. It subclasses Gitlab::Git::CommandError, so existing callers that rescue CommandError keep working.
  • Unwraps the new Gitaly::ReferenceUnresolvableError detail (nested in Gitaly::RefExistsError) into the typed error in WrapsGitalyErrors, so callers get an actionable error instead of a generic CommandError.
  • Self-heals in Gitlab::Git::KeepAround#execute: when the keep-around ref is dangling, it logs and deletes the ref, then continues. If the commit is present, the ref is re-created; if it is gone, the SHA is skipped. #kept_around? itself still raises, so only execute deletes refs.
  • Rescues the errors delete_refs can raise (Repository::GitError, ReferencesLockedError, InvalidRefFormatError) in execute. These are not CommandError subclasses, so without this a failed delete would escape into inline after_commit callers. A failed delete is tracked and the SHA is returned for retry.

The gitaly gem already includes the new proto from gitaly!8967 (merged), so no gem bump is needed.

References

Verification

Check Before this MR With this MR
repository.ref_exists?(ref) on the dangling ref Raised Gitlab::Git::CommandError Raised the typed Gitlab::Git::ReferenceUnresolvableError, with the ref name and missing object ID
Gitlab::Git::KeepAround.execute(repository, [sha], source: :verifier) Returned the SHA in the failed-SHAs array; the dangling ref remained Returned []; the dangling ref was removed and a valid keep-around ref was created
Repeat the keep-around call with the valid ref Not tested Returned []; the ref remained valid

How to set up and validate locally

  1. Use a Gitaly version that includes gitaly!8967 (merged).
  2. Fabricate a dangling keep-around ref in a test repo: write a commit, point refs/keep-around/<oid> at it, then delete the loose object at objects/<oid[0:2]>/<oid[2:]>.
  3. In the Rails console:
    p = Project.find(<id>)
    p.repository.ref_exists?("refs/keep-around/<oid>")
    # => raises Gitlab::Git::ReferenceUnresolvableError
    
    p.repository.keep_around("<oid>", source: 'manual')
    # => [] ; logs 'Deleting dangling keep-around reference' and deletes the ref
    
    p.repository.ref_exists?("refs/keep-around/<oid>")
    # => false

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Vasilii Iakliushin

Merge request reports

Loading
Loading