craftz

Merged branches

craftz gone deletes each local branch whose branch on GitHub is gone. GitHub deletes a branch once its pull request merges, so that's each one that merged. A repository runs it as pnpm gone, and it works where no git alias is set, like an agent's machine.

A squash merge puts a new commit on main, so git can't tell the branch merged, and it's deleted with --force. When the branch you're on is one of them, it switches to main and pulls first.

import { execFileSync } from "node:child_process";

const run = (...args: string[]): void => {
  execFileSync("git", args, { stdio: "inherit" });
};
const output = (...args: string[]): string =>
  execFileSync("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();

/** The branches in `git for-each-ref`'s lines whose upstream is gone. */
export const goneIn = (refs: string): string[] =>
  refs
    .split("\n")
    .filter((line) => line.endsWith(" [gone]"))
    .map((line) => line.slice(0, -" [gone]".length));

/** Deletes each local branch that merged. */
export function gone(): void {
  run("fetch", "--quiet", "--prune");
  const branches = goneIn(
    output("for-each-ref", "--format=%(refname:short) %(upstream:track)", "refs/heads"),
  );
  if (branches.includes(output("branch", "--show-current"))) {
    run("switch", "--quiet", "main");
    run("pull", "--quiet", "--prune");
  }
  for (const branch of branches) run("branch", "--delete", "--force", branch);
  if (!branches.length) console.log("No merged branches to delete.");
}