Push mirror deletes branch from destination when changes and branch deletion occur within the same sync interval
### Summary When a branch has new changes pushed and is then deleted within the same sync interval (5 minutes, or 1 minute for protected branches), the remote mirror deletes the branch regardless of whether it had unmerged changes. This contradicts the documented behavior which states that branches with unmerged changes should be kept on the remote mirror. ### Steps to reproduce 1. Create a branch in the source repository 2. Wait 5 minutes for the branch to sync to the remote mirror 3. Push new changes/commits to the branch (do **not** merge into the default branch) 4. Delete the branch immediately (within the same sync interval, before the changes from step 3 are synced) 5. Wait 5 minutes for the next sync 6. **Result:** The remote mirror deletes the branch, even though it contained unmerged changes ### Expected behavior Per the [Push mirroring documentation](https://docs.gitlab.com/ee/user/project/repository/mirror/push.html): > When a branch is merged into the default branch and deleted in the source project, it is deleted from the remote mirror on the next push. Branches with unmerged changes are kept. The branch should be **kept** on the remote mirror because it contained unmerged changes at the time of deletion. ### Actual behavior The branch is deleted from the remote mirror even though it had unmerged changes that were never synced. ### Root cause > **Note:** This root cause analysis was generated by GitLab Duo based on a review of the codebase and may not be fully accurate. Manual verification is recommended. The push mirror sync is event-driven: each push to the source repository triggers `Repositories::PostReceiveWorker`, which calls `project.update_remote_mirrors`, scheduling a `RepositoryUpdateRemoteMirrorWorker` job with a backoff delay (5 minutes default, 1 minute for protected branches via `RemoteMirror#backoff_delay`). When the worker eventually runs, it calls `RemoteMirror#update_repository`, which delegates to Gitaly's `UpdateRemoteMirror` RPC via `Gitlab::GitalyClient::RemoteService#update_remote_mirror`. This RPC compares the **current local repository state** against the remote mirror to determine what refs to create, update, or delete. The problem is that when both a push (step 3) and a branch deletion (step 4) occur within the same backoff interval, the `RepositoryUpdateRemoteMirrorWorker` coalesces both events. The worker uses `RemoteMirror#updated_since?` to skip redundant runs, so only the last scheduled job executes. By the time this job runs, the branch has already been deleted from the local repository. Gitaly sees no local branch and instructs the remote to delete it, with no opportunity to evaluate whether the branch had unmerged changes, because the branch ref no longer exists locally. In short, the "branches with unmerged changes are kept" guarantee only works when the branch still exists in the local repository at the time Gitaly performs the comparison. The backoff-based coalescing of mirror updates means intermediate states (branch with new commits) are never seen by Gitaly if a subsequent deletion removes the branch before the sync runs. ### Workaround Wait for the full sync interval (5 minutes, or 1 minute if "Only mirror protected branches" is enabled) between pushing changes and deleting the branch. This allows the changes to sync to the remote mirror first, enabling Gitaly to correctly identify the branch as having unmerged changes and preserve it on the remote.
issue