Skip to content

Redesign sidecar sync: baseline bootstrap + incremental patch#339

Draft
michael-webster wants to merge 1 commit into
mainfrom
webster/sync-redesign
Draft

Redesign sidecar sync: baseline bootstrap + incremental patch#339
michael-webster wants to merge 1 commit into
mainfrom
webster/sync-redesign

Conversation

@michael-webster
Copy link
Copy Markdown
Contributor

Summary

Replaces the current sync mechanism (merge-base recalculation + GitHub clone on every first sync) with a two-phase design:

  • Bootstrap (sidecar setup or stale baseline): clones from GitHub if workspace absent, otherwise git fetch; checks out exact local HEAD when pushed or merge-base otherwise; applies a patch of all local changes; stores BaselineRef + BaselineBranch
  • Incremental sync (sidecar sync, same branch, valid baseline): generates git diff <BaselineRef> --binary locally, resets sidecar to BaselineRef, applies patch — no GitHub access needed after initial setup

Staleness detection re-bootstraps automatically when the branch changes, no baseline is stored, or BaselineRef is no longer an ancestor of HEAD (rebase/force-push).

Also fixes MergeBase() fallback: was returning git rev-parse origin/HEAD (the remote tip, potentially ahead of local HEAD); now correctly uses git merge-base HEAD origin/HEAD.

Remote commands use sh -c "cd <path> && git ..." instead of git -C which is unavailable on the sidecar's older git version.

Changes

  • internal/sidecar/active.go — add BaselineRef, BaselineBranch fields
  • internal/gitutil/gitutil.go — add HeadRef, IsAncestor; fix MergeBase fallback
  • internal/sidecar/sync.go — replace syncWorkspace/Sync with Bootstrap/Sync; remove errApplyFailed retry logic
  • internal/cmd/sidecar.gosidecarSetupSync calls Bootstrap; sync command passes cwd
  • internal/sidecar/sync_test.go — updated for new behaviour
  • acceptance/sidecar_test.go — sync flag test now runs from a git repo

Test plan

  • All unit and acceptance tests pass (go test -race ./...)
  • E2E script (dev-utils/e2e-sync.sh) passes all 4 scenarios on a live sidecar:
    • Fresh bootstrap (no baseline) → clone/fetch + patch applied
    • Incremental sync (same branch) → patch only, no re-clone
    • Branch switch → re-bootstrap triggered
    • Stale baseline (simulated rebase) → re-bootstrap triggered

🤖 Generated with Claude Code

Replaces the merge-base-recalculation-on-every-sync approach with a
two-phase design that is more robust and GitHub-independent after setup.

Bootstrap (sidecar setup / stale baseline):
- Clones from GitHub if workspace absent, otherwise git fetch to update
- Checks out exact local HEAD if branch is pushed, merge-base otherwise
- Applies a patch covering all local changes from the checkout point
- Stores BaselineRef + BaselineBranch in sidecar state

Incremental sync (sidecar sync, same branch, valid baseline):
- Generates git diff <BaselineRef> --binary locally
- Resets sidecar to BaselineRef, applies patch — no GitHub access needed

Staleness detection re-bootstraps automatically when:
- No baseline stored (first sync after create)
- Branch changed since last bootstrap
- BaselineRef is no longer an ancestor of HEAD (rebase/force-push)

Also fixes MergeBase() fallback: was returning git rev-parse origin/HEAD
(the remote tip, potentially ahead of local HEAD); now uses
git merge-base HEAD origin/HEAD for the actual common ancestor.

Remote commands use sh -c "cd <path> && git ..." to support the
sidecar's SSH server without requiring git -C (unavailable on old git).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@michael-webster michael-webster marked this pull request as draft May 18, 2026 20:55
@schurchleycci
Copy link
Copy Markdown
Contributor

This seems like a good approach. A few Claude comments:

Issue 1: Growing patch size — Confirmed

In Sync (sync.go:~210), after successfully applying the patch, active.BaselineRef is never updated. The function loads the stored baseline, runs GeneratePatch(active.BaselineRef), resets, applies — then returns. Nothing writes a new BaselineRef back to state. Every subsequent call diffs from the original bootstrap SHA, so the patch grows linearly with accumulated commits. The fix would be to save active.BaselineRef = headSHA before returning.

Issue 2: Double reset in Bootstrap — Confirmed

The sequence in Bootstrap is:

git reset --hard HEAD && git clean -fd (pre-checkout)
git checkout
git reset --hard && git clean -fd (post-checkout, comment: “handles any detached HEAD drift”)
After step 2, HEAD is already at checkoutSHA in detached HEAD state. No other code runs between steps 2 and 3, so there’s nothing that could cause drift. Step 3 is an unconditional extra SSH round-trip with no effect.

Issue 3: Test coverage gaps — Confirmed

sync_test.go has exactly one test function. It covers only the stale-baseline-triggers-Bootstrap path (and specifically the sub-case where MergeBase fails). There is no test for:

The happy-path incremental sync: valid BaselineRef, same branch → patch applied, no re-bootstrap
The branch-changed trigger specifically: BaselineBranch != currentBranch → Bootstrap called
These two are the core decision paths in the new Sync logic (needsBootstrap evaluation) and aren’t exercised at all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants