Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save rgruesbeck/77e76370f985ff983a636df2234e94ce to your computer and use it in GitHub Desktop.

Select an option

Save rgruesbeck/77e76370f985ff983a636df2234e94ce to your computer and use it in GitHub Desktop.
fast-rebase
# Upstream Sync Strategy: Moving to a High-Cadence Rebase Workflow
## Goal
Shorten the time between an upstream release and our own release by moving
from "merge upstream into develop, fix issues, release quarterly" to a
high-cadence (weekly) sync process, automated where possible with a human
in the loop for safety, backed by automated E2E tests to catch regressions
introduced by unreleased upstream code.
## Rebase vs. Merge: the real trade-off
**Rebase-onto-upstream** ("vendor branch" / "patch stack" model, used by
Linux distros, Chromium embedders, many Android forks) keeps our
customizations as a clean, readable patch series on top of upstream.
- Benefits: our diff vs. upstream is always inspectable
(`git log upstream/develop..develop` *is* our customizations), conflicts
stay small because we resolve them weekly against fresh upstream, and old
resolved conflicts don't come back to haunt us.
- Costs: every rebase **rewrites history** and force-pushes our develop
branch, which disrupts anyone with open feature branches or in-flight
PRs. We also re-resolve the *same* conflicts every week unless we use
`git rerere`.
**Merge at high cadence** (weekly merges instead of quarterly) gets most of
the "small conflicts, fast integration" benefit without rewriting history.
The downside is history becomes a merge-lattice and our net diff vs.
upstream is harder to see.
**Middle path:** maintain our customizations as a rebased patch series
internally, but cut releases from a separate, immutable branch so the
rewritten branch is never something others build long-lived work on top
of directly.
**Recommendation:** if the team is small and the customization set is
modest (roughly under ~50 commits of delta), weekly rebase is very
automatable and gives the clearest picture of "what we changed." If many
people branch off develop daily, weekly merges may cause less friction.
The conflict-detection and E2E gating described below work for either
choice.
## Recommended architecture (rebase variant)
### Branch model
- `upstream/develop` — the upstream remote's develop branch.
- `patches` — our rewritten branch, rebased weekly onto
`upstream/develop`. This branch force-pushes; it is the source of truth
for "our delta."
- Feature work happens on short-lived branches off `patches`, merged via
PR before the weekly rebase when possible.
- Release branches/tags are cut from `patches` and are immutable, so
releases are never affected by later rewrites.
### Weekly automated job (CI, e.g. GitHub Actions / GitLab CI)
1. `git fetch upstream`.
2. If upstream has no new commits, exit quietly.
3. Attempt `git rebase upstream/develop` on a fresh candidate branch
(e.g. `rebase/2026-07-13`), with `git rerere` enabled and a shared
rerere cache (commit the rerere cache to a side branch/artifact so CI
reuses past resolutions — this is what makes weekly rebasing cheap).
4. **Clean rebase:** push the candidate branch, run the full E2E suite
against it, and open a PR ("Weekly upstream sync — clean, E2E
green/red"). A human approves, and the branch becomes the new
`patches` (human-in-the-loop safety valve).
5. **Conflicts:** the job stops and opens an issue/PR with the
conflicting commits/files, pinging the owning team. A human finishes
the rebase locally, pushes the candidate, and CI re-runs E2E.
### Key mechanics
- **`git rerere` with a shared cache** — records conflict resolutions so
identical conflicts auto-resolve on later rebases. Without this, weekly
rebasing is Groundhog Day.
- **Curate the patch stack** — periodically squash/reorder our delta
commits so each one is coherent and minimal. Fewer, cleaner patches
means fewer conflicts. Upstream anything upstreamable — every patch we
delete is conflicts we never see again.
- **Isolate customizations structurally** where possible (separate files,
plugin/hook points, config overlays instead of edits to upstream
files). This is the highest-leverage long-term move: conflicts only
happen where we touch upstream lines.
- **E2E as the merge gate, not just a smoke test** — run it on the
candidate branch before it becomes `patches`. Also run E2E nightly
against raw `upstream/develop` so we learn about upstream breakage
*before* our sync day, and can distinguish "upstream is broken" from
"our rebase broke it."
- **Track delta size as a metric** — number of patches, lines of diff vs.
upstream, conflicts per sync. A rising trend signals accumulating fork
debt.
## Tooling options for the automation
1. **Plain CI workflow (recommended starting point)** — a scheduled
GitHub Action (~100 lines of bash) doing
fetch → rebase-with-rerere → push candidate → open PR. Simple,
transparent, easy to debug. Weekly cadence via `schedule: cron`.
2. **Existing sync tools** — GitHub's "Sync fork" API / `gh repo sync`
(merge-based only), apps like Pull or Backstroke (merge-based, less
actively maintained). Nothing off-the-shelf does rebase +
human-in-the-loop well, which is why most forks hand-roll option 1.
3. **Agent-assisted conflict resolution** — have the CI job invoke an
agent (e.g. `claude -p`) to propose conflict resolutions on the
candidate branch, but always land the result via PR with E2E green +
human review. Treat it strictly as a proposer, never an auto-merger.
This can turn "human must do the rebase" into "human reviews a
proposed resolution," a meaningful cadence win.
## Suggested rollout
1. **Now:** add the `upstream` remote, enable `rerere`, do one manual
rebase to establish the clean patch stack. This first one is the
painful one — it collapses years of merge history into a curated
series. Consider `git rebase --onto` from the last upstream merge
point rather than replaying full history.
2. **Week 2–3:** stand up the scheduled CI job that attempts the rebase
and opens a PR; wire E2E to run on candidate branches. Add nightly
E2E against raw upstream too.
3. **Then:** tighten cadence, add the shared rerere cache, optionally add
agent-assisted conflict proposals, and start a standing effort to
shrink the patch stack (upstream contributions, structural
isolation).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment