diff --git a/src/cli.ts b/src/cli.ts index 45e0ccb..b191e59 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -16,6 +16,7 @@ import { action as rebaseAction } from "./commands/rebase.ts" import { action as saveAction } from "./commands/save.ts" import { action as diffAction } from "./commands/diff.ts" import { action as showAction } from "./commands/show.ts" +import { action as browseAction } from "./commands/browse.ts" import { action as logAction } from "./commands/log.ts" import { action as dirAction } from "./commands/dir.ts" import { action as editAction } from "./commands/edit.ts" @@ -102,6 +103,12 @@ program .description("Show the prompt and full diff for a branch") .action(showAction) +program + .command("browse") + .argument("", "branch name") + .description("Open the branch diff in a web browser") + .action(browseAction) + program .command("save") .argument("", "branch name") diff --git a/src/commands/browse.ts b/src/commands/browse.ts new file mode 100644 index 0000000..eb474e8 --- /dev/null +++ b/src/commands/browse.ts @@ -0,0 +1,96 @@ +import { $ } from "bun" +import * as git from "../git.ts" +import { die } from "../fmt.ts" +import { requireSession } from "./helpers.ts" + +export async function action(branch: string) { + const { session } = await requireSession(branch) + const worktree = session.worktree + const main = await git.mainBranch(worktree) + + const [diff, log, stat] = await Promise.all([ + git.branchDiff(branch, worktree), + git.commitLog(`${main}..${branch}`, worktree), + git.diffStat(`${main}...${branch}`, worktree), + ]) + + if (!diff.trim()) { + die(`No changes on branch "${branch}" compared to ${main}.`) + } + + const prompt = session.prompt ? escapeHtml(session.prompt) : "" + const diffJson = JSON.stringify(diff) + + const html = ` + + + +${escapeHtml(branch)} — sandlot diff + + + + +
+

${escapeHtml(branch)}

+ ${prompt ? `

${prompt}

` : ""} +
+ ${log ? `

Commits

${escapeHtml(log)}
` : ""} + ${stat ? `

Stats

${escapeHtml(stat)}
` : ""} +
+
+
+ + + +` + + const tmpPath = `/tmp/sandlot-browse-${branch}.html` + await Bun.write(tmpPath, html) + await $`open ${tmpPath}`.nothrow().quiet() +} + +function escapeHtml(str: string): string { + return str + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) +} diff --git a/src/git.ts b/src/git.ts index 379e139..21cf5d3 100644 --- a/src/git.ts +++ b/src/git.ts @@ -218,6 +218,14 @@ export async function hasNewCommits(worktreePath: string): Promise { return parseInt(result.text().trim(), 10) > 0 } +/** Get the full unified diff of a branch vs main as a string. */ +export async function branchDiff(branch: string, cwd: string): Promise { + const main = await mainBranch(cwd) + const result = await $`git diff ${main}...${branch}`.cwd(cwd).nothrow().quiet() + if (result.exitCode !== 0) return "" + return result.text() +} + /** Detect the main branch name (main or master). */ export async function mainBranch(cwd?: string): Promise { const dir = cwd ?? "."