This document records the one-time authorship-preservation cutover performed on 2026-04-29 against the public foundry-samples repository. It is preserved as a runbook so that a future similar surgery (a sync-pipeline replacement, an authorship rewrite, a public-repo history reset) has a known-good template.
If you are a regular contributor and have arrived here looking for "how does sync work day to day," you want Repo Sync Automation. This document is for one-off pipeline surgery.
Before the cutover, the sync pipeline used a different rewriting model that caused every synced commit on public to appear authored by a single human (the workflow's then-author). Public git blame was therefore useless for attribution.
After the cutover:
- The sync rewrites authorship using
.github/sync-mailmap, mapping internal aliases to the contributor's public identity. - Public
git blameshows the real author of each line. - Bot-authored commits (the sync App,
github-actions[bot]) are filtered out of the rewritten stream. - A
legacy/main-pre-authorship-cutoverbranch andpre-authorship-cutover-2026-04-29tag preserve the old public history.
The cutover required a force-push of public main to a fully rewritten history; standard merges would have produced a Frankenstein log mixing old single-author commits with new rewritten ones.
| Reference | What it points to |
|---|---|
Tag pre-authorship-cutover-2026-04-29 (public repo) |
Last commit of the pre-cutover public main |
Branch legacy/main-pre-authorship-cutover (public repo) |
Same commit, kept as a branch for easier checkout |
Tag pre-authorship-cutover-2026-04-29 (private repo) |
Private-side snapshot at cutover time |
Recovering the old state, if ever needed, is a reset to either reference followed by a force-push (subject to the same ruleset disablement step described below).
| Reference | Value (at cutover time) |
|---|---|
Public main head |
f532c159ac80d60e5edc46693733801fee0c7469 |
| Sync App (bypass actor) | foundry-samples-repo-sync (App ID 2846614) |
| Bot user ID | 261063410 |
Public main ruleset ID |
6131793 |
- Confirm both repos are at green CI on
main. - Confirm the new sync pipeline (filter-stream, mailmap, fast-export/fast-import) has been validated against a dry-run target. The dry run should produce a tree-equivalent
mainto what's currently public, with rewritten authorship. - Stop the nightly sync schedule for the duration of the cutover (workflow disabled in the UI).
- Notify stakeholders that public
mainwill force-push.
In the public repo:
git fetch origin
git tag pre-authorship-cutover-$(date -u +%Y-%m-%d) origin/main
git push origin pre-authorship-cutover-$(date -u +%Y-%m-%d)
git push origin origin/main:refs/heads/legacy/main-pre-authorship-cutoverIn the private repo, tag the matching state:
git tag pre-authorship-cutover-$(date -u +%Y-%m-%d) origin/main
git push origin pre-authorship-cutover-$(date -u +%Y-%m-%d)Before the force-push, enumerate every path on public main that lives under an excluded path in sync-config.json (or matches an excluded file) and is therefore not present in the rewritten stream. These are the files that will be wiped by the force-push and will need restoration afterward.
# In a clone of public repo
git ls-tree -r --name-only origin/main > /tmp/public-paths.txt
# Cross-reference against exclude_pathspecs from sync-config.json — anything
# matching an exclude is public-only and at risk.Typical public-only paths:
README.md,CONTRIBUTING.md(public-only versions of the private files).github/CODEOWNERS(public review routing).github/workflows/*(any workflow that only runs on public — e.g., PR redirect, public-side checks).github/scripts/*(helpers used by those workflows).github/copilot-instructions.md(if maintained separately on public)
Save the full list — you will replay it onto the new history in step 5a.
The branch ruleset on public main blocks force-push even for bypass actors when certain rules are enabled. Set the ruleset to "Disabled" in the UI (Settings → Rules → Rulesets → Edit → Enforcement status: Disabled) for the duration of the force-push.
A ruleset disable is recoverable and audited; deleting and recreating the ruleset is not. Use disable.
Run the new sync pipeline in non-incremental mode (force_full=true) targeting public main directly. The pipeline will:
- Stream all of private
mainthroughfilter-stream.pywith.github/sync-mailmapapplied. - Fast-import into a fresh ref on the public side.
- Force-push that ref to
refs/heads/main.
Confirm the resulting main head SHA matches the dry-run prediction before proceeding.
Set the ruleset back to "Active" in the UI. Verify that the App is still listed as a bypass actor; the disable/re-enable cycle should not have removed it, but check anyway.
Watch out for
Require approval of the most recent reviewable push. If this rule was on before the cutover, leave it off for the new sync model — it interferes with the App's direct-merge flow. See Repo Sync Automation § Public Repo Branch Protection & App Bypass.
Run the Verify Sync workflow on the public repo (workflow_dispatch of verify-sync.yml). It should report drift=false. If it doesn't, stop and investigate before re-enabling the schedule — drift right after a force-push almost always indicates a path-exclusion or filter bug.
Spot-check public git blame on a few recently-edited samples; the authors should be the real contributors, not the App or a single human placeholder.
The force-push wiped everything that was not in the rewritten stream — including the public-only files inventoried in step 1a. Open a PR on the public repo that restores those files verbatim from legacy/main-pre-authorship-cutover:
git fetch origin 'refs/heads/legacy/main-pre-authorship-cutover:refs/remotes/origin/legacy/main-pre-authorship-cutover'
git checkout -b restore/public-only-files-from-cutover
for f in <list-from-step-1a>; do
mkdir -p "$(dirname "$f")"
git checkout origin/legacy/main-pre-authorship-cutover -- "$f"
done
git commit -m "Restore public-only files lost during cutover"
gh pr create --base mainDo not skip this step. The 2026-04-29 cutover did skip it (the runbook didn't yet contain it) and the public repo lost its README, CONTRIBUTING, and several public-only workflows including PR redirect and PR checks. The omission was caught only when a contributor noticed the README missing days later — see foundry-samples PR #676.
Re-enable the nightly sync workflow. Confirm the next scheduled run completes as a no-op (no new private commits since cutover) or as a small delta (only commits since cutover).
If the cutover produces an unexpected state and rollback is required:
- Disable the ruleset (step 2 above).
git push --force origin refs/tags/pre-authorship-cutover-YYYY-MM-DD:refs/heads/mainfrom a local clone with admin/bypass credentials.- Re-enable the ruleset.
- Disable the sync schedule until the underlying issue is resolved.
The legacy/main-pre-authorship-cutover branch is kept indefinitely as a secondary recovery path.
The cutover surfaced three issues that were tracked separately:
- Auto-merge bypass not inherited. GitHub's
--automerge runs as the system process, not the requesting actor, so the App's bypass permission was not applied. Resolved by replacing--autowith poll-then-direct-merge (wait-and-merge.sh). See Repo Sync Automation § Wait-and-merge. - CodeQL
actionsanalysis on public. The public repo hadactionsenabled in default-setup CodeQL, producing false positives because the public repo has no workflow YAML to analyse (post-cutover). Resolved by uncheckingactionsin the public repo's Code Security settings. - Public-only files wiped by the force-push. Step 1a / 5a above did not exist in the original runbook; as a result, README.md, CONTRIBUTING.md, and seven public-only
.github/workflows / scripts were lost and had to be restored later. The runbook has been updated to make the inventory and replay steps explicit.
Three reasons:
- Authorship can't be rewritten in-place. Git commits are immutable; rewriting authorship produces new SHAs for every commit. There is no way to "convert" the existing public history to use the new authors.
- A merge of rewritten history into existing history would double every commit. Public would contain both the old single-author commit and the new rewritten commit for every change.
git blamewould still resolve to the latest commit — usually the rewritten one — but the log would be twice as long and confusing forever. - The legacy branch and tag preserve the old state. Anyone who wants to inspect pre-cutover history can check out the legacy branch; the cost of the force-push is therefore just the disruption of one moment, not permanent loss of history.
- Repo Sync Automation — How the post-cutover sync works day-to-day
- Validation Contract — Validation responsibilities (no longer cross-coupled with sync)
- Filter stream script — Authorship rewriting + path filtering
- Sync mailmap — Internal alias → public identity mapping