Skip to content

Add entire doctor migrate-checkpoints (git-branch → git-refs)#1611

Open
Soph wants to merge 3 commits into
mainfrom
feat/checkpoint-migrate
Open

Add entire doctor migrate-checkpoints (git-branch → git-refs)#1611
Soph wants to merge 3 commits into
mainfrom
feat/checkpoint-migrate

Conversation

@Soph

@Soph Soph commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

https://entire.io/gh/entireio/cli/trails/735

Adds a migration that converts checkpoints on the entire/checkpoints/v1 branch into per-checkpoint refs under refs/entire/checkpoints/<shard>/<id> — the layout the git-refs store uses — so a repo on the git-branch store can move to git-refs.

How it works

checkpoint.MigrateBranchToRefs(ctx, repo, dryRun) walks the v1 branch tip and, for each checkpoint:

  • Takes the checkpoint's current subtree object and wraps it in a fresh commit (existing branch commits are not remapped), then points the ref at it.
  • Because the git-refs ref tree is the branch's <shard>/<id> subtree byte-for-byte, a migrated checkpoint reads identically under either backend.

Idempotent: a checkpoint whose ref already carries the same tree is skipped; a changed checkpoint re-migrates by fast-forward (the new commit parents on the existing ref, so nothing is lost from history). Safe to re-run after more branch activity.

The command

entire doctor migrate-checkpoints — reports migrated / already-up-to-date / total.

Push behavior (as requested): the function only enqueues; the command decides whether to push:

  • Interactive → prompts "Push N migrated checkpoint ref(s) now?"
  • Non-interactive → never pushes; refs stay queued and flush on the next git push once git-refs is the primary store.
  • --dry-run reports what would migrate without writing refs.

Push reuse

The git-refs pre-push queue-flush is extracted into strategy.flushCheckpointRefsQueue, shared by the fail-soft pre-push path and the new error-surfacing strategy.PushMigratedCheckpointRefs. So "push now" goes through the exact same fast-forward + fetch/replay recovery logic (no force). No behavior change to pre-push.

Testing

  • New migrate_test.go: basic migration + byte-identical ref trees + read-back via the git-refs store; idempotent re-run (all skipped, refs unmoved); advance-on-branch-change (fast-forward parenting); dry-run writes nothing; no-branch no-op.
  • Build clean, lint 0 issues (the flush extraction keeps dupl quiet). Checkpoint + strategy unit suites pass; integration 392; git-refs canary 58/58 +1 skip, roger 4/4.

Not included (follow-ups)

  • ULID emission, and pushing a git-branch mirror for downgrade safety — unchanged from the git-refs store's existing scope.

Refs: #1471

🤖 Generated with Claude Code


Note

Medium Risk
Migration rewrites local git refs and checkpoint data layout; push behavior differs between interactive opt-in and fail-soft pre-push, though logic is shared and tested.

Overview
Adds entire doctor migrate-checkpoints so repos on the git-branch checkpoint store can move to the git-refs layout (refs/entire/checkpoints/<shard>/<id>).

checkpoint.MigrateBranchToRefs walks checkpoints on entire/checkpoints/v1, wraps each checkpoint’s current subtree in a new commit, and points the per-checkpoint ref at it (enqueueing push like normal git-refs writes). It is idempotent (same tree → skip; changed tree → fast-forward parent) and supports --dry-run. Unmappable IDs are skipped with a warning.

The doctor subcommand reports migrated / skipped / total counts. Interactive runs can opt into an immediate push; non-interactive runs leave refs queued for the next git push.

strategy.flushCheckpointRefsQueue extracts the git-refs push-queue drain logic from pre-push; pre-push still fail-soft on errors, while PushMigratedCheckpointRefs surfaces errors for the migration “push now” path. migrate_test.go covers migration fidelity, idempotency, branch updates, dry-run, and no-branch no-op.

Reviewed by Cursor Bugbot for commit 856c7ca. Configure here.

Copilot AI review requested due to automatic review settings July 2, 2026 18:45
@Soph Soph requested a review from a team as a code owner July 2, 2026 18:45

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 856c7ca. Configure here.

result, err := checkpoint.MigrateBranchToRefs(ctx, repo, dryRun)
if err != nil {
return fmt.Errorf("migrate checkpoints: %w", err)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ctrl+C shows noisy migrate error

Low Severity

The migrate-checkpoints RunE wraps errors from MigrateBranchToRefs and PushMigratedCheckpointRefs with fmt.Errorf without mapping context.Canceled to NewSilentError, so interrupting the command prints a noisy Cobra error instead of exiting quietly.

Additional Locations (1)
Fix in Cursor Fix in Web

Triggered by learned rule: Map context.Canceled to NewSilentError on user cancellation

Reviewed by Cursor Bugbot for commit 856c7ca. Configure here.

if err != nil {
return fmt.Errorf("push migrated refs: %w", err)
}
fmt.Fprintf(out, "Pushed %d checkpoint ref(s).\n", pushed)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disabled push reports zero pushed

Medium Severity

After the user confirms pushing migrated refs, PushMigratedCheckpointRefs returns (0, nil) when push_sessions is disabled, and the command still prints Pushed 0 checkpoint ref(s). even though nothing was sent to the remote and refs remain queued locally.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 856c7ca. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a doctor-time migration path to move existing checkpoints from the legacy single branch store (entire/checkpoints/v1) into the per-checkpoint ref layout used by the git-refs checkpoint store (refs/entire/checkpoints/<shard>/<id>), enabling repos to transition storage backends without rewriting branch history.

Changes:

  • Add entire doctor migrate-checkpoints to migrate v1-branch checkpoint subtrees into per-checkpoint ref commits, with --dry-run and optional interactive “push now”.
  • Implement checkpoint.MigrateBranchToRefs plus unit tests validating byte-identical trees, idempotency, fast-forward advancement, and dry-run behavior.
  • Extract/refactor checkpoint-ref queue flushing in manual_commit_push.go to share logic between pre-push (fail-soft) and migration “push now” (error-surfacing).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
cmd/entire/cli/strategy/manual_commit_push.go Extracts queued checkpoint-ref flush into a shared helper and adds an error-surfacing push entrypoint for migration.
cmd/entire/cli/doctor.go Registers the new doctor migrate-checkpoints subcommand.
cmd/entire/cli/doctor_migrate.go Implements the doctor migrate-checkpoints command, including interactive “push now” prompt and dry-run flag.
cmd/entire/cli/checkpoint/migrate.go Adds the core migration routine to convert v1-branch checkpoints into per-checkpoint refs.
cmd/entire/cli/checkpoint/migrate_test.go Adds tests covering migration correctness, idempotency, advancement, dry-run, and no-branch no-op.

Comment on lines +100 to +104
if err := refsStore.setRef(ctx, cid, commitHash); err != nil {
return fmt.Errorf("set ref for checkpoint %s: %w", cid, err)
}
result.Migrated = append(result.Migrated, cid)
return nil
Comment on lines +184 to +190
repo, err := OpenRepository(ctx)
if err != nil {
return 0, fmt.Errorf("open repository: %w", err)
}
defer repo.Close()
return flushCheckpointRefsQueue(ctx, repo, ps.pushTarget())
}
Comment thread cmd/entire/cli/doctor_migrate.go Outdated
Comment on lines +16 to +18
// migrateCheckpointsPushRemote is the remote the opt-in "push now" targets; the
// actual push URL is resolved from checkpoint-remote settings when configured.
const migrateCheckpointsPushRemote = "origin"
Comment on lines +78 to +85
parent := plumbing.ZeroHash
if existing, err := repo.Reference(refName, true); err == nil {
parent = existing.Hash()
if commit, cerr := repo.CommitObject(parent); cerr == nil && commit.TreeHash == cpTreeHash {
result.Skipped++
return nil
}
}
// the existing ref when present (re-migration fast-forwards) or as an
// orphan for a brand-new ref — then point the ref at it and enqueue it.
msg := fmt.Sprintf("Import checkpoint %s (migrated from git-branch)", cid)
commitHash, err := CreateCommit(ctx, repo, cpTreeHash, parent, msg, authorName, authorEmail)

@pfleidi pfleidi Jul 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we decided to commit the existing subtree objects only, but Paulo brought up a good point here:

With that in mind, the current CheckpointVersion is stored in metadata.json. This blocks us from migrating from branch to refs without changing the checkpoint metadata itself. As a result, every migrated checkpoint would require a new Blob for the updated metadata.json and a new Tree to hold it, on top of the new Commit we would need regardless. Which is not ideal, as one of the properties of the migration was co-existence of both formats with minimum overhead.

We already have a similar issue with SessionFilePaths, which treats paths differently depending on whether the checkpoint was created for a branch or for a ref.

Part of me is wondering whether we should update both the CheckpointVersion as well as the SessionFilePaths during the migration since this information wouldn't reflect the reality of the new storage location anymore:

  • The CheckpointVersion would still point to branch-v1
  • SessionFilePaths would still point to the shard path relative to the root of the checkpoint branch and not relative to their new storage location

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW: Part of me is also wondering whether we should remove the CheckpointVersion completely given that "If you can read this, you already know how to read it" 😓

Soph and others added 3 commits July 3, 2026 15:25
Convert checkpoints on the entire/checkpoints/v1 branch into per-checkpoint refs
(refs/entire/checkpoints/<shard>/<id>), the git-refs store's layout.

checkpoint.MigrateBranchToRefs walks the v1 branch tip and, for each checkpoint,
wraps its CURRENT subtree object in a fresh commit and points the ref at it —
existing branch commits are not remapped. Since the git-refs ref tree is the
branch's <shard>/<id> subtree byte-for-byte, a migrated checkpoint reads
identically under either backend. It is idempotent: a checkpoint whose ref
already carries the same tree is skipped, and a changed checkpoint re-migrates by
fast-forward (new commit parents on the existing ref, so no history is lost).
New/advanced refs are enqueued for push; the function itself never pushes.

The `doctor migrate-checkpoints` command reports migrated/skipped/total and, per
the requested policy, only pushes when it can prompt: interactively it asks
whether to push now; non-interactively it never pushes (refs stay queued and
flush on the next push once git-refs is primary). `--dry-run` reports what would
change without writing refs.

Push reuse: the git-refs pre-push queue-flush is extracted into
strategy.flushCheckpointRefsQueue (shared by the fail-soft pre-push path and the
new error-surfacing strategy.PushMigratedCheckpointRefs), so the "push now"
option goes through the exact same fast-forward + fetch/replay logic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: ceed5d17e529
…ntRefs

- doctor migrate-checkpoints now reuses the existing confirmDoctorFix helper
  instead of a near-duplicate confirm form — picking up its context-cancellation
  guard (huh opens the TTY during startup regardless of ctx state) and dropping
  the huh/errors/context imports.
- PushMigratedCheckpointRefs takes the *git.Repository the command already
  opened rather than re-opening one, matching the strategy entry-point pattern
  and removing a redundant repo open per invocation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 01f324c13852
…remote, parent validation)

- Map context.Canceled to NewSilentError so Ctrl-C exits quietly instead of
  printing a noisy Cobra error (matches the clean.go convention). [cursor]
- When the user confirms push but checkpoint pushing is disabled in settings,
  report that the refs stay queued instead of "Pushed 0 checkpoint ref(s)".
  [cursor]
- Add a --remote flag (default origin) instead of hardcoding "origin", so repos
  that push to a different remote work. [copilot]
- MigrateBranchToRefs now only uses an existing ref as the new commit's parent
  when it resolves to a real commit; a ref at an unreadable/non-commit object is
  treated as absent (orphan) rather than parenting on a bad hash, which would
  corrupt the commit graph for fetch+replay. [copilot]

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 5d352fa1b1bb
@Soph Soph force-pushed the feat/checkpoint-migrate branch 2 times, most recently from 06b3c99 to 96537c1 Compare July 4, 2026 08:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants