From 156e4d959069a2854336affb4f3f04bff6ad28b5 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Wed, 25 Feb 2026 20:23:35 -0800 Subject: [PATCH] Add checkout command with co alias --- src/cli.ts | 14 ++++++++++++++ src/commands/checkout.ts | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 src/commands/checkout.ts diff --git a/src/cli.ts b/src/cli.ts index 97a7629..bd980c3 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -10,6 +10,7 @@ import { action as openAction } from "./commands/open.ts" import { action as reviewAction } from "./commands/review.ts" import { action as shellAction } from "./commands/shell.ts" import { action as closeAction } from "./commands/close.ts" +import { action as checkoutAction } from "./commands/checkout.ts" import { action as mergeAction } from "./commands/merge.ts" import { action as squashAction } from "./commands/squash.ts" import { action as rebaseAction } from "./commands/rebase.ts" @@ -81,6 +82,19 @@ program .description("Remove a session (alias for close)") .action((branch: string, opts: { force?: boolean }) => closeAction(branch, opts)) +program + .command("checkout") + .argument("", "branch name") + .option("-f, --force", "checkout even if there are unsaved changes") + .description("Close the session and check out the branch locally") + .action((branch: string, opts: { force?: boolean }) => checkoutAction(branch, opts)) + +program + .command("co", { hidden: true }) + .argument("", "branch name") + .option("-f, --force", "checkout even if there are unsaved changes") + .action((branch: string, opts: { force?: boolean }) => checkoutAction(branch, opts)) + // ── Branch ────────────────────────────────────────────────────────── program.commandsGroup("Branch Commands:") diff --git a/src/commands/checkout.ts b/src/commands/checkout.ts new file mode 100644 index 0000000..86afe69 --- /dev/null +++ b/src/commands/checkout.ts @@ -0,0 +1,35 @@ +import { join } from "path" +import { unlink } from "fs/promises" +import * as git from "../git.ts" +import * as vm from "../vm.ts" +import * as state from "../state.ts" +import { die } from "../fmt.ts" + +export async function action(branch: string, opts: { force?: boolean } = {}) { + const root = await git.repoRoot() + const session = await state.getSession(root, branch) + + if (!session) { + die(`No session found for branch "${branch}"`) + } + + const worktreeAbs = session.worktree + + if (!opts.force && await git.isDirty(worktreeAbs)) { + die(`Branch "${branch}" has unsaved changes. Run "sandlot save ${branch}" first, or use -f to force.`) + } + + await vm.clearActivity(worktreeAbs, branch) + + await git.removeWorktree(worktreeAbs, root) + .catch((e) => console.warn(`⚠ Failed to remove worktree: ${e.message}`)) + + await unlink(join(root, '.sandlot', branch)) + .catch(() => {}) // symlink may not exist + + await state.removeSession(root, branch) + + await git.checkout(branch, root) + + console.log(`✔ Checked out ${branch}`) +}