@biffo/cli 0.300.1 → 0.301.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +89 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1281,6 +1281,17 @@ var GitAdapter = class {
|
|
|
1281
1281
|
const { stdout, exitCode } = await execa("git", ["rev-parse", "HEAD"], { cwd, reject: false });
|
|
1282
1282
|
return exitCode === 0 ? stdout.trim() : null;
|
|
1283
1283
|
}
|
|
1284
|
+
/**
|
|
1285
|
+
* `branch`'s own tip SHA, without checking it out — the bare-branch
|
|
1286
|
+
* counterpart to `headSha` (#1682 milestone 2). A branch with no linked
|
|
1287
|
+
* worktree has no `cwd` of its own to run `rev-parse HEAD` against, so this
|
|
1288
|
+
* resolves the ref by name instead; `cwd` only needs to be *any* worktree of
|
|
1289
|
+
* the same clone; local refs are shared across all of them.
|
|
1290
|
+
*/
|
|
1291
|
+
async branchSha(cwd, branch) {
|
|
1292
|
+
const { stdout, exitCode } = await execa("git", ["rev-parse", branch], { cwd, reject: false });
|
|
1293
|
+
return exitCode === 0 ? stdout.trim() : null;
|
|
1294
|
+
}
|
|
1284
1295
|
/**
|
|
1285
1296
|
* Is `ancestor` reachable from `descendant` (including `ancestor ===
|
|
1286
1297
|
* descendant`)? Three-valued, not boolean: `git merge-base --is-ancestor`
|
|
@@ -15037,6 +15048,43 @@ async function reapAll(cwd, branches, worktrees, currentBranch, deps) {
|
|
|
15037
15048
|
}
|
|
15038
15049
|
return outcomes;
|
|
15039
15050
|
}
|
|
15051
|
+
function findBareBranchCandidates(branches, worktrees) {
|
|
15052
|
+
const worktreeBranches = new Set(worktrees.map((w) => w.branch));
|
|
15053
|
+
return branches.filter((b) => b.track.includes("gone") && !worktreeBranches.has(b.name)).map((b) => ({ branch: b.name }));
|
|
15054
|
+
}
|
|
15055
|
+
async function reapBareBranch(cwd, candidate, deps) {
|
|
15056
|
+
const { git, github } = deps;
|
|
15057
|
+
const prVerdict = await github.prVerdictForBranch(cwd, candidate.branch);
|
|
15058
|
+
let mergeContainsHead = true;
|
|
15059
|
+
if (prVerdict === "merged") {
|
|
15060
|
+
const [branchTip, mergedHeadSha] = await Promise.all([
|
|
15061
|
+
git.branchSha(cwd, candidate.branch),
|
|
15062
|
+
github.mergedHeadSha(cwd, candidate.branch)
|
|
15063
|
+
]);
|
|
15064
|
+
mergeContainsHead = branchTip === null || mergedHeadSha === null ? null : await git.isAncestor(cwd, branchTip, mergedHeadSha);
|
|
15065
|
+
}
|
|
15066
|
+
const verdict = classifyReapCandidate({
|
|
15067
|
+
isDetached: false,
|
|
15068
|
+
isDirty: false,
|
|
15069
|
+
prVerdict,
|
|
15070
|
+
mergeContainsHead
|
|
15071
|
+
});
|
|
15072
|
+
if (verdict.action === "keep") {
|
|
15073
|
+
return { candidate, verdict, branchDeleted: null };
|
|
15074
|
+
}
|
|
15075
|
+
const branchDeleted = await git.deleteBranch(cwd, candidate.branch);
|
|
15076
|
+
return { candidate, verdict, branchDeleted };
|
|
15077
|
+
}
|
|
15078
|
+
async function reapAllBareBranches(cwd, branches, worktrees, currentBranch, deps) {
|
|
15079
|
+
const candidates = findBareBranchCandidates(branches, worktrees).filter(
|
|
15080
|
+
(c) => c.branch !== currentBranch
|
|
15081
|
+
);
|
|
15082
|
+
const outcomes = [];
|
|
15083
|
+
for (const candidate of candidates) {
|
|
15084
|
+
outcomes.push(await reapBareBranch(cwd, candidate, deps));
|
|
15085
|
+
}
|
|
15086
|
+
return outcomes;
|
|
15087
|
+
}
|
|
15040
15088
|
|
|
15041
15089
|
// src/commands/doctor.ts
|
|
15042
15090
|
var INTEGRATION_BRANCH = "dev";
|
|
@@ -15044,7 +15092,7 @@ var doctorCommand = new Command25("doctor").description(
|
|
|
15044
15092
|
"Report repo-state conditions that make everything read from this checkout unreliable"
|
|
15045
15093
|
).option("--cwd <path>", "Repo root to inspect (defaults to the current directory)").option("--no-fetch", "Skip the fetch; report against refs as they already are locally").option(
|
|
15046
15094
|
"--fix",
|
|
15047
|
-
"Also remove any worktree PROVEN safe: one whose branch's PR merged, verified via GitHub (never local commit reachability \u2014 a squash merge rewrites every SHA), AND whose current
|
|
15095
|
+
"Also remove any worktree, and delete any bare local branch, PROVEN safe: one whose branch's PR merged, verified via GitHub (never local commit reachability \u2014 a squash merge rewrites every SHA), AND whose current tip is confirmed contained in what that PR actually shipped \u2014 not merely on a branch of the same name (#1810). Everything else (no PR, PR open, PR closed unmerged, detached HEAD, uncommitted changes, commits ahead of what merged, or that containment could not be confirmed) is reported, never touched."
|
|
15048
15096
|
).action(async (options) => {
|
|
15049
15097
|
const cwd = options.cwd ? resolve20(options.cwd) : process.cwd();
|
|
15050
15098
|
const git = new GitAdapter();
|
|
@@ -15053,8 +15101,11 @@ var doctorCommand = new Command25("doctor").description(
|
|
|
15053
15101
|
const findings = runDoctorChecks(facts);
|
|
15054
15102
|
printFindings(findings);
|
|
15055
15103
|
if (options.fix === true) {
|
|
15056
|
-
const
|
|
15104
|
+
const github = new GithubCliAdapter();
|
|
15105
|
+
const outcomes = await runDoctorFix(cwd, facts, { git, github });
|
|
15057
15106
|
printReapOutcomes(outcomes);
|
|
15107
|
+
const branchOutcomes = await runDoctorFixBranches(cwd, facts, { git, github });
|
|
15108
|
+
printBranchReapOutcomes(branchOutcomes);
|
|
15058
15109
|
}
|
|
15059
15110
|
if (findings.some((f) => f.severity === "error")) process.exit(1);
|
|
15060
15111
|
} catch (err) {
|
|
@@ -15104,6 +15155,9 @@ async function gatherRepoFacts(options, deps) {
|
|
|
15104
15155
|
async function runDoctorFix(cwd, facts, deps = { git: new GitAdapter(), github: new GithubCliAdapter() }) {
|
|
15105
15156
|
return reapAll(cwd, facts.branches, facts.worktrees, facts.currentBranch, deps);
|
|
15106
15157
|
}
|
|
15158
|
+
async function runDoctorFixBranches(cwd, facts, deps = { git: new GitAdapter(), github: new GithubCliAdapter() }) {
|
|
15159
|
+
return reapAllBareBranches(cwd, facts.branches, facts.worktrees, facts.currentBranch, deps);
|
|
15160
|
+
}
|
|
15107
15161
|
function readLocalCoreVersion(cwd) {
|
|
15108
15162
|
const path = join61(cwd, INSTANCE_CORE_FILE);
|
|
15109
15163
|
if (!existsSync53(path)) return null;
|
|
@@ -15204,6 +15258,39 @@ function printReapOutcomes(outcomes) {
|
|
|
15204
15258
|
)
|
|
15205
15259
|
);
|
|
15206
15260
|
}
|
|
15261
|
+
function printBranchReapOutcomes(outcomes) {
|
|
15262
|
+
if (outcomes.length === 0) {
|
|
15263
|
+
console.log(chalk21.dim(" --fix (branches): no bare branch with a gone upstream to consider.\n"));
|
|
15264
|
+
return;
|
|
15265
|
+
}
|
|
15266
|
+
const attempted = outcomes.filter((o) => o.verdict.action === "reap");
|
|
15267
|
+
const kept = outcomes.filter((o) => o.verdict.action === "keep");
|
|
15268
|
+
const deleted = attempted.filter((o) => o.branchDeleted === true);
|
|
15269
|
+
const failed = attempted.filter((o) => o.branchDeleted !== true);
|
|
15270
|
+
console.log("");
|
|
15271
|
+
for (const o of attempted) {
|
|
15272
|
+
if (o.branchDeleted === true) {
|
|
15273
|
+
console.log(chalk21.green(` deleted ${o.candidate.branch}`));
|
|
15274
|
+
} else {
|
|
15275
|
+
console.log(
|
|
15276
|
+
chalk21.red(
|
|
15277
|
+
` FAILED ${o.candidate.branch} \u2014 judged safe to delete but 'git branch -D' did not succeed; left as-is`
|
|
15278
|
+
)
|
|
15279
|
+
);
|
|
15280
|
+
}
|
|
15281
|
+
}
|
|
15282
|
+
for (const o of kept) {
|
|
15283
|
+
const reason = o.verdict.reason === void 0 ? "unknown" : KEEP_REASON_TEXT[o.verdict.reason];
|
|
15284
|
+
console.log(chalk21.dim(` kept ${o.candidate.branch} \u2014 ${reason}`));
|
|
15285
|
+
}
|
|
15286
|
+
console.log(
|
|
15287
|
+
chalk21.dim(
|
|
15288
|
+
`
|
|
15289
|
+
--fix (branches): ${String(deleted.length)} deleted, ${String(failed.length)} failed, ${String(kept.length)} kept, of ${String(outcomes.length)} branch(es) considered.
|
|
15290
|
+
`
|
|
15291
|
+
)
|
|
15292
|
+
);
|
|
15293
|
+
}
|
|
15207
15294
|
|
|
15208
15295
|
// src/commands/teardown.ts
|
|
15209
15296
|
import { execSync as execSync7 } from "child_process";
|