feat(gitsync): mirror pull request heads as destination branches - #43
Merged
Conversation
clouddrove-ci
changed the base branch from
fix/private-repo-discovery
to
master
August 19, 2026 14:01
clouddrove-ci
force-pushed
the
feat/pr-sync-p1
branch
from
August 19, 2026 14:02
afc9fdd to
62b1993
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #42. Review that one first; the base retargets to
masteronce it merges.What
Phase 1 of four in the pull request replication program. The commits behind an open pull request now reach the destination as an ordinary branch, and the provider independent model the later phases build on is defined.
A pull request opened from a fork has no branch in the source repository, so today its commits exist nowhere in the mirror. With
pull_requests.enabledon a mirror, the head is pushed asrefs/heads/syncerd/pr/<number>.The pull request object itself is not recreated at the destination. That is P2.
Two vendor facts that shaped this
Checked against vendor documentation on 2026-08-19, not assumed:
refs/merge-requests/<iid>/head14 days after the merge request closes.refs/pull-requests/*/fromas an internal implementation detail and states that any integration using it "is going to be racy and unreliable because that ref is not eagerly updated".refs/pull/<id>/merge, which is absent whenever the pull request has conflicts.So a ref-based strategy cannot be the only one. The head is fetched from the fork's own clone URL and head branch, which every provider's pull request API reports.
The load-bearing design decision
The head lands in the clone cache as an ordinary branch, not in a private namespace. Everything downstream then works unchanged:
--prunedeletes the destination branch once the pull request closes and SyncerD stops fetching its head, so cleanup needs no bookkeepingadditiveandfast-forwardbehave for these branches exactly as they do for any otherThe cache's own
fetch --prunedoes not cover these branches, since they are not tracked by the origin refspec, soPrunePRBranchesremoves the ones that are no longer wanted.Behaviour worth calling out
pull-requestsstage.Security: read before enabling
Off by default, deliberately. A fork pull request head is code written by anyone who can open a pull request against the source. This pushes it to a destination branch, where it arrives as an ordinary push from a trusted mirror rather than as a fork pull request, so none of the source's fork-PR protections apply. If destination CI builds on branch push, it runs that code with whatever credentials that CI holds.
The runbook says this next to the setting and recommends excluding the
branch_prefixnamespace from destination CI. The prefix is one contiguous namespace so a single glob covers it.Scope
GitHub sources only. A mirror enabling
pull_requestswith any other source type is rejected at startup with a message naming the phase. GitLab, Bitbucket, and Azure DevOps arrive in P4 behind the same interfaces.Testing
Provider tests cover pagination, fork versus same-repository heads, deleted forks, merged-state mapping, and no token leakage on error. Engine tests use real local git repositories and prove the destination branch appears, that a same-repository head gets no copy, that a new pull request defeats the unchanged skip, and that the branch is pruned from the destination after the pull request closes.
verify-syncerdfull tier passes.