Upstream Rebase Process
Gallery is a fork of Immich. Keeping the fork current is risky because Gallery has a large fork-owned surface area across server, web, mobile, machine learning, CI, generated clients, and database migrations. A clean Git conflict resolution is not enough: upstream can change extension points that compile but silently remove Gallery behavior.
This process keeps upstream syncs reviewable by making fork ownership explicit, planning the rebase in small batches, and running targeted audits after each batch.
Source Of Truth
Fork ownership is tracked in:
docs/fork/ownership.yml
The manifest is the source of truth for:
- fork-owned files and directories
- upstream extension paths where Gallery behavior is injected into upstream code
- expected symbols that must survive in extension files
- Gallery-owned database migrations and migration globs
- shipped mobile Drift versions
- generated artifacts that need explicit review
- CI workflow invariants
- package patches
- risk patterns and required checks
Do not reconstruct fork ownership from memory or from an agent skill. If a fork feature is missing from the manifest, update the manifest first and run the coverage check.
Local Setup
Fetch the current fork and upstream refs:
git fetch origin main --no-tags
git fetch upstream main --no-tags
Use a fresh worktree for the upstream rebase:
git worktree remove --force .claude/worktrees/upstream-rebase 2>/dev/null || true
git worktree add .claude/worktrees/upstream-rebase origin/main
cd .claude/worktrees/upstream-rebase
git checkout -B rebase/upstream-batched
Do not run the rebase directly on main. Use the temporary branch until every
batch is verified and the final result is approved.
Manifest Baseline
The manifest records the fork head it was last verified against:
metadata:
fork_remote: origin
fork_branch: main
last_verified_fork_head: <full origin/main sha>
Before starting an upstream sync, confirm the manifest is current:
make fork-ownership-coverage-check
This target treats last_verified_fork_head as the reviewed floor for the
manifest, not as a permanent lock to the current fork head. It:
- Lists fork files with
git diff --name-only upstream/main...origin/main. - Checks that every fork file is covered by
docs/fork/ownership.yml, unless it is explicitly ignored. - Compares
metadata.last_verified_fork_headwithgit rev-parse origin/main.
Baseline outcomes:
- Exact match: the manifest is current.
- Baseline is an ancestor of
origin/main: the check passes with warnings and lists files changed since the manifest was verified. - Baseline is not an ancestor of
origin/main: the check fails because the manifest baseline does not describe the current fork history.
The coverage check always runs against the full fork delta, not only files changed since the manifest baseline. The changed-since-baseline list is a review aid for manifest maintenance.
Broad optional manifest globs such as mobile/**, docs/**, or
server/src/** are allowed as safety nets. Files changed after the manifest
baseline that are covered only by those broad optional globs produce warnings.
To make those warnings blocking during manifest cleanup, run:
pnpm --filter @gallery/upstream-preflight run coverage -- /tmp/gallery-fork-files.txt docs/fork/ownership.yml --expected-head "$(git rev-parse origin/main)" --strict-broad-coverage
Preflight
Run the readiness command before rebasing:
make upstream-rebase-ready
Readiness writes fresh preflight, batch plan, and readiness reports under Git metadata:
$(git rev-parse --git-path upstream-preflight)
It fails for blockers such as uncovered fork files, non-ancestor manifest baselines, CI invariant failures, package patch failures, and current fork integrity failures. It passes with warnings for ancestor baseline drift and broad optional coverage. Known upstream work such as mobile Drift renumbering, generated artifact review, and migration timestamp collisions is listed as planned resolution work.
You can still run the preflight report directly:
make upstream-preflight
The report is printed to stdout and written under:
git rev-parse --git-path upstream-preflight
The preflight report includes:
- merge base
- incoming upstream commits and files
- fork delta files
- direct overlap files
- high-risk commits
- dependency and lockfile changes
- server migration and table overlap signals
- mobile Drift collision signals
- CI workflow signals
- broad refactor hints
- audit signals
- recommended batch plan
- fork surface reduction signals
Review the report before rebasing. Pay particular attention to high-risk commits, direct overlaps, mobile Drift collisions, generated artifacts, and workflow changes. The fork-surface section is advisory and should be interpreted with the fork surface guidelines.
Batch Plan
Generate the batch plan:
make upstream-batch-plan
The planner prints Markdown and writes the persisted plan under Git metadata:
$(git rev-parse --git-path upstream-preflight)/batch-plan.md
$(git rev-parse --git-path upstream-preflight)/batch-plan.json
The JSON includes the merge base, manifest-defined upstream and fork refs, the current upstream and fork heads, the manifest baseline, generation time, and the batch soft cap. Batch tips and commit entries are stored as full SHAs; Markdown uses short SHAs only for readability.
The planner keeps upstream commit order and groups low-risk commits together up to a soft cap. It isolates high-risk commits and commits that touch multiple manifest features or generated API artifacts.
Each batch includes:
- batch id
- upstream tip SHA
- commit count
- risk level
- whether it is a checkpoint
- reasons
- cheap post-batch checks
- expensive checkpoint checks
- exact operator commands
Example:
git rebase <batch-tip-sha>
make upstream-postrebase-audit BATCH=02
make mobile-drift-rebase-check BATCH=02
make e2e-rebase-smoke
git push origin HEAD:rebase/upstream-batch-02 --force
Cheap checks run after every affected batch. Expensive checks and remote pushes only render at checkpoints. Checkpoints are selected conservatively: every high-risk batch, any low/medium run after roughly ten cumulative upstream commits, and the final batch.
Review and approve the batch plan before starting the rebase.
Rebase Mechanics
Before each rebase step, ask the tool which persisted batch is next:
make upstream-next-batch
This validates that batch-plan.json still matches the current upstream ref,
then prints the next batch id, full tip SHA, risk, reasons, and exact commands.
If upstream moved, rerun make upstream-batch-plan and review the new plan
before continuing.
Rebase the fork stack from one upstream batch tip to the next. The command
printed by make upstream-next-batch uses the full persisted batch tip:
git rebase <full-batch-tip-sha>
The equivalent manual form is:
BASE="$(git merge-base origin/main upstream/main)"
NEXT="<batch-tip-sha>"
git rebase --onto "$NEXT" "$BASE" HEAD
After a batch succeeds, run the printed audit commands and then run
make upstream-next-batch again. The tool derives completed batches from
HEAD, so operators do not need to track BASE or NEXT manually. Repeat
until it reports that the branch already includes upstream/main.
When conflicts occur, record the conflict resolution:
### Conflict: <file path>
- Fork side: <what Gallery was doing>
- Upstream side: <what upstream changed>
- Resolution: <what was kept or adapted and why>
- Risk: LOW / MEDIUM / HIGH
- Verification needed: <specific command or manual check>
For conflict-resolved files, compare against upstream to catch accidental deletions:
git diff upstream/main..HEAD -- <file>
For lockfile conflicts, usually take upstream, then regenerate from the repo root:
pnpm install --no-frozen-lockfile
Review the resulting lockfile diff before continuing.
Post-Batch Audits
After every batch, run the post-rebase audit with the batch id:
make upstream-postrebase-audit BATCH=NN
With BATCH=NN, generated artifact and migration-collision signals are scoped
to that batch's planned upstream commits. Without BATCH, the command inspects
the full incoming upstream range.
Batch reports are written under:
$(git rev-parse --git-path upstream-preflight)/batches/
The post-rebase audit checks:
- literal fork-owned files still exist
- expected symbols still exist in extension paths
- Gallery migration count matches manifest expectations
- expected Gallery migration filenames still exist
- migration globs still match files
- incoming upstream migration timestamps do not collide with Gallery migrations
- generated OpenAPI, mobile OpenAPI, and SQL artifacts are reviewed
A generated artifact issue is not automatically a bad rebase. It means the affected artifacts must be regenerated or explicitly reviewed before final push.
Mobile Drift
Mobile Drift migrations are high risk because bad version handling can corrupt existing mobile databases.
Run the full backlog check before rebasing:
make mobile-drift-rebase-check
On a backlog where upstream touches shipped Gallery Drift versions, this command should fail and tell you to renumber incoming upstream migrations above Gallery's current highest shipped version.
After an individual batch, scope the check:
make mobile-drift-rebase-check BATCH=NN
The batch-scoped command only considers Drift collision signals from that batch's planned commits.
Rules:
- Keep Gallery's shipped versions unchanged.
- Append incoming upstream migrations above Gallery's highest version.
- Keep
schemaVersionequal to the highest checked-in snapshot. - Keep
mobile/drift_schemas/main/drift_schema_vN.jsoncontiguous. - Keep every
fromNToN+1callback present exactly once. - Stop for review if any Drift renumbering is non-additive or ambiguous.
For the current v23/v24 collision pattern, the expected strategy is:
- Gallery v23 and v24 remain Gallery's shipped migrations.
- Upstream's original v23 becomes the next available version.
- Upstream's original v24 becomes the version after that.
- Snapshots and callbacks are appended above Gallery's current highest version.
CI And Patch Checks
Run these when make upstream-next-batch prints them after affected batches and
before final push:
make ci-invariants-check
make fork-patches-check
make ci-invariants-check verifies manifest-defined workflow invariants, such
as avoiding upstream PUSH_O_MATIC token dependencies and keeping Gallery image
names in release workflows.
make fork-patches-check verifies package patch metadata, including expected
patch files and references in pnpm-workspace.yaml.
Generated Artifacts
If preflight or post-rebase audits report generated artifacts, regenerate them after the affected upstream batch:
make build-server
make open-api
make sql
make sql may require a local development database. If generation cannot run
locally, keep the audit issue visible and use CI output to complete the
regeneration before the final push.
Generated batch reports and preflight reports are written to Git metadata, not
the working tree. They should not appear in git status.
Final Verification
Before asking to update main, run:
pnpm install --frozen-lockfile
make fork-ownership-coverage-check
make upstream-rebase-ready
make upstream-preflight
make upstream-batch-plan
make upstream-next-batch
make upstream-postrebase-audit
make ci-invariants-check
make fork-patches-check
pnpm --filter @gallery/upstream-preflight run test
pnpm --filter @gallery/upstream-preflight run check
pnpm --filter @gallery/upstream-preflight run format
Also run the required checks listed by each batch and any domain-specific suites for touched areas.
Expected current-backlog signals are allowed only when they match the known planning signals:
- unbatched
make mobile-drift-rebase-checkreports shipped Gallery v23/v24 collisions and tells you to renumber incoming upstream migrations - unbatched
make upstream-postrebase-auditreports generated artifacts that need review or regeneration make upstream-postrebase-audit BATCH=01reports only batch 01 generated artifacts
Unexpected audit failures should stop the rebase until they are understood.
Current Backlog Notes
A disposable rehearsal against upstream/main at
7acda0572dc3349977d1aa66e90a3ef1474583fa confirmed these expected stops for
the current backlog:
- Batch 01 rebases after one web conflict in
web/src/lib/components/asset-viewer/detail-panel-tags.svelte. Preserve upstream'sBadge onCloseAPI while keeping Gallery's owner-only tag removal behavior for space members. - Batch 01 post-rebase audit reports generated artifact review for
server/src/queries/asset.job.repository.sql. - Batch 02 reaches the known mobile Drift conflict on fork commit
12edb59cd. The conflicted files includemobile/drift_schemas/main/drift_schema_v23.json, generated Drift files, andmobile/lib/infrastructure/repositories/db.repository.dart. Resolve this during the real rebase by keeping Gallery v23/v24 shipped versions and appending incoming upstream migrations above Gallery's current highest version.
Final Push
Before force-pushing main, create a backup branch:
git fetch origin main --no-tags
git branch "backup/main-before-upstream-rebase-$(date +%Y%m%d-%H%M)" origin/main
Confirm the final branch contains:
- all current
origin/maincommits - all approved fork commits replayed on upstream
- all resolved conflicts
- all regenerated artifacts needed by the rebase
Prefer the push-rebase skill for the final force push so missing PR commits
are detected before origin/main is updated.
After pushing, babysit required CI until green. Do not merge or force-push a partially verified upstream sync.
Troubleshooting
The manifest coverage check fails
If files are uncovered, add the missing ownership entries or coverage ignores.
If only last_verified_fork_head is stale, refresh it to git rev-parse origin/main after confirming the manifest still covers the current fork diff.
A batch audit reports future-batch files
Use BATCH=NN. Batch-scoped audits should use only that batch's planned
commits. If an audit still reports future-batch files, regenerate the batch plan
and inspect the batch id.
The mobile Drift check fails after a batch
If the batch touches shipped Gallery Drift versions, resolve the collision by
keeping Gallery's shipped versions and appending upstream migrations above them.
If the batch does not touch those versions, rerun with BATCH=NN and inspect
the batch plan.
CI invariant checks fail
Read the exact workflow path and forbidden pattern in the output. Either restore the Gallery invariant or add a narrow manifest exception only when the exception is intentional and documented.
Patch checks fail
Confirm the expected patch file exists and pnpm-workspace.yaml references it.
Do not remove a fork patch just to make the check pass; verify whether the patch
is still required against the upstream version.