From 988509622985587c3e3010e70f74f89e68b8ddf0 Mon Sep 17 00:00:00 2001 From: Mplan Date: Thu, 11 Jun 2026 18:24:53 +0800 Subject: [PATCH] feat(cli): add branch push check before PR creation --- index.ts | 26 ++++++++++++++++++++++++++ src/pr.ts | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/index.ts b/index.ts index 5cd8cd2..696246f 100644 --- a/index.ts +++ b/index.ts @@ -23,6 +23,8 @@ import type { Config } from "./src/types"; import { getDefaultBranch, getBranchName, + getBranchPushStatus, + pushCurrentBranch, getBranchCommits, getBranchDiff, detectPlatform, @@ -524,6 +526,30 @@ async function handlePR(draft: boolean): Promise { ` ${commits.length} commit${commits.length > 1 ? "s" : ""} on this branch`, ); + const pushStatus = await getBranchPushStatus(); + if (!pushStatus.pushed) { + const target = pushStatus.upstream ?? `origin/${branchName}`; + const answer = await ask( + ` Branch is not pushed to ${CYAN}${target}${RESET}. Push now? [${GREEN}Y${RESET}/n] `, + ); + + if (answer.toLowerCase() === "n") { + console.log(" Aborted."); + return; + } + + console.log(` Pushing ${CYAN}${branchName}${RESET}...`); + try { + await pushCurrentBranch(branchName, pushStatus.upstream); + console.log(` ${GREEN}Pushed ${branchName}.${RESET}`); + } catch (err) { + console.error( + ` ${RED}Push failed: ${err instanceof Error ? err.message : err}${RESET}`, + ); + process.exit(1); + } + } + const diff = await getBranchDiff(baseBranch); if (!diff) { console.error(` ${RED}Error: No diff from base branch.${RESET}`); diff --git a/src/pr.ts b/src/pr.ts index aba9bb7..1bd758e 100644 --- a/src/pr.ts +++ b/src/pr.ts @@ -25,6 +25,38 @@ export async function getBranchName(): Promise { return result.trim(); } +export async function getBranchPushStatus(): Promise<{ + pushed: boolean; + upstream: string | null; +}> { + try { + const upstream = (await Bun.$`git rev-parse --abbrev-ref --symbolic-full-name @{u}`.quiet().text()).trim(); + const localHead = (await Bun.$`git rev-parse HEAD`.quiet().text()).trim(); + const remoteHead = (await Bun.$`git rev-parse @{u}`.quiet().text()).trim(); + return { pushed: localHead === remoteHead, upstream }; + } catch { + return { pushed: false, upstream: null }; + } +} + +export async function pushCurrentBranch(branch: string, upstream: string | null): Promise { + const args = upstream + ? ["push"] + : ["push", "-u", "origin", branch]; + + const proc = Bun.spawn(["git", ...args], { + stdout: "pipe", + stderr: "pipe", + }); + const exitCode = await proc.exited; + const stdout = await new Response(proc.stdout).text(); + const stderr = await new Response(proc.stderr).text(); + + if (exitCode !== 0) { + throw new Error(stderr.trim() || stdout.trim() || `git push failed (exit code ${exitCode})`); + } +} + export async function getBranchCommits(base: string): Promise { try { const result =