@biffo/cli 0.309.2 → 0.309.3
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.
|
@@ -170,10 +170,18 @@ because `--auto` waits for ever on a check that cannot re-evaluate itself (a
|
|
|
170
170
|
|
|
171
171
|
## 6. Push honestly, and verify the remote has your commit
|
|
172
172
|
|
|
173
|
-
- **Push with the exit status visible
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
173
|
+
- **Push with the exit status visible, and record the upstream:**
|
|
174
|
+
`git push -u origin HEAD; echo $?`. A pipe (`git push | tail`) reports the
|
|
175
|
+
pipe's status, not git's — a rejected push then reads as success and the
|
|
176
|
+
commit is silently lost. Never trust a "pushed" message printed
|
|
177
|
+
unconditionally after a pipe.
|
|
178
|
+
- **The `-u` is not optional (biffo-template#1954).** `git worktree add -b
|
|
179
|
+
<branch> <path> origin/dev` sets the new branch's upstream to `origin/dev`
|
|
180
|
+
by default — a plain `git push origin HEAD`, with no `-u`, never corrects
|
|
181
|
+
it. A branch left tracking `origin/dev` can be pushed, merged, and have its
|
|
182
|
+
own remote copy deleted, while `git branch -vv` never reports it `[gone]`
|
|
183
|
+
(the ref it is actually tracking never goes anywhere) — so `doctor --fix`
|
|
184
|
+
and every other tool reading that marker stays permanently blind to it.
|
|
177
185
|
- **Confirm the remote actually has the commit before relying on it,** especially
|
|
178
186
|
before merge: `git log origin/<branch> -1`. A green PR page is not proof your
|
|
179
187
|
latest local commit reached it.
|
|
@@ -170,10 +170,18 @@ because `--auto` waits for ever on a check that cannot re-evaluate itself (a
|
|
|
170
170
|
|
|
171
171
|
## 6. Push honestly, and verify the remote has your commit
|
|
172
172
|
|
|
173
|
-
- **Push with the exit status visible
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
173
|
+
- **Push with the exit status visible, and record the upstream:**
|
|
174
|
+
`git push -u origin HEAD; echo $?`. A pipe (`git push | tail`) reports the
|
|
175
|
+
pipe's status, not git's — a rejected push then reads as success and the
|
|
176
|
+
commit is silently lost. Never trust a "pushed" message printed
|
|
177
|
+
unconditionally after a pipe.
|
|
178
|
+
- **The `-u` is not optional (biffo-template#1954).** `git worktree add -b
|
|
179
|
+
<branch> <path> origin/dev` sets the new branch's upstream to `origin/dev`
|
|
180
|
+
by default — a plain `git push origin HEAD`, with no `-u`, never corrects
|
|
181
|
+
it. A branch left tracking `origin/dev` can be pushed, merged, and have its
|
|
182
|
+
own remote copy deleted, while `git branch -vv` never reports it `[gone]`
|
|
183
|
+
(the ref it is actually tracking never goes anywhere) — so `doctor --fix`
|
|
184
|
+
and every other tool reading that marker stays permanently blind to it.
|
|
177
185
|
- **Confirm the remote actually has the commit before relying on it,** especially
|
|
178
186
|
before merge: `git log origin/<branch> -1`. A green PR page is not proof your
|
|
179
187
|
latest local commit reached it.
|
package/dist/index.js
CHANGED
|
@@ -1449,6 +1449,39 @@ var GitAdapter = class {
|
|
|
1449
1449
|
const { stdout, exitCode } = await execa("git", ["rev-parse", "HEAD"], { cwd, reject: false });
|
|
1450
1450
|
return exitCode === 0 ? stdout.trim() : null;
|
|
1451
1451
|
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Every local branch name that currently has a same-named remote-tracking
|
|
1454
|
+
* ref under `refs/remotes/origin/` (biffo-template#1954) — i.e. `git
|
|
1455
|
+
* for-each-ref refs/remotes/origin`, stripped of the `refs/remotes/origin/`
|
|
1456
|
+
* prefix, with the `HEAD` symref excluded (it names no branch).
|
|
1457
|
+
*
|
|
1458
|
+
* This is the fact `doctor --fix`'s reap-candidate detection needs instead
|
|
1459
|
+
* of trusting a branch's own *configured* upstream (`%(upstream:track)`,
|
|
1460
|
+
* i.e. `git branch -vv`'s `[gone]` marker): `git worktree add -b <branch>
|
|
1461
|
+
* <path> origin/dev` sets the new branch's upstream to `origin/dev` by
|
|
1462
|
+
* default, and a plain `git push origin HEAD` (no `-u`) never corrects
|
|
1463
|
+
* it — so a branch can be pushed, merged, and have its OWN remote copy
|
|
1464
|
+
* deleted, while its `[gone]` marker never fires because the thing it is
|
|
1465
|
+
* actually tracking (`origin/dev`) never goes anywhere. Checking for a
|
|
1466
|
+
* same-named ref sidesteps whatever the branch happens to be configured to
|
|
1467
|
+
* track entirely.
|
|
1468
|
+
*
|
|
1469
|
+
* Absence from this set does NOT by itself prove a branch's remote copy
|
|
1470
|
+
* was deleted — a branch never pushed under its own name is equally
|
|
1471
|
+
* absent. See `doctor-reaper.ts`'s `hasProvenGoneRemote` for why that
|
|
1472
|
+
* distinction is safe to skip here (unlike in `upgrade-branch-reaper.ts`,
|
|
1473
|
+
* which has no downstream verification and must not skip it).
|
|
1474
|
+
*/
|
|
1475
|
+
async listRemoteBranchNames(cwd) {
|
|
1476
|
+
const { stdout } = await execa(
|
|
1477
|
+
"git",
|
|
1478
|
+
["for-each-ref", "--format=%(refname:lstrip=3)", "refs/remotes/origin"],
|
|
1479
|
+
{ cwd }
|
|
1480
|
+
);
|
|
1481
|
+
return new Set(
|
|
1482
|
+
stdout.split("\n").map((line) => line.trim()).filter((name) => name !== "" && name !== "HEAD")
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1452
1485
|
/**
|
|
1453
1486
|
* `branch`'s own tip SHA, without checking it out — the bare-branch
|
|
1454
1487
|
* counterpart to `headSha` (#1682 milestone 2). A branch with no linked
|
|
@@ -16300,8 +16333,13 @@ function classifyReapCandidate(facts) {
|
|
|
16300
16333
|
return { action: "keep", reason: "unknown-pr-verdict" };
|
|
16301
16334
|
}
|
|
16302
16335
|
}
|
|
16303
|
-
function
|
|
16304
|
-
|
|
16336
|
+
function hasProvenGoneRemote(branch, remoteBranchNames) {
|
|
16337
|
+
return !remoteBranchNames.has(branch.name);
|
|
16338
|
+
}
|
|
16339
|
+
function findReapCandidates(branches, worktrees, remoteBranchNames) {
|
|
16340
|
+
const goneBranches = new Set(
|
|
16341
|
+
branches.filter((b) => hasProvenGoneRemote(b, remoteBranchNames)).map((b) => b.name)
|
|
16342
|
+
);
|
|
16305
16343
|
return worktrees.filter((w) => goneBranches.has(w.branch)).map((w) => ({ branch: w.branch, worktreePath: w.path }));
|
|
16306
16344
|
}
|
|
16307
16345
|
async function reapCandidate(cwd, candidate, deps) {
|
|
@@ -16345,8 +16383,8 @@ async function reapCandidate(cwd, candidate, deps) {
|
|
|
16345
16383
|
const worktreeRemoved = await git.removeWorktree(cwd, candidate.worktreePath);
|
|
16346
16384
|
return { candidate, verdict, worktreeRemoved, staleClaimCleared };
|
|
16347
16385
|
}
|
|
16348
|
-
async function reapAll(cwd, branches, worktrees, currentBranch, deps) {
|
|
16349
|
-
const candidates = findReapCandidates(branches, worktrees).filter(
|
|
16386
|
+
async function reapAll(cwd, branches, worktrees, currentBranch, deps, remoteBranchNames) {
|
|
16387
|
+
const candidates = findReapCandidates(branches, worktrees, remoteBranchNames).filter(
|
|
16350
16388
|
(c) => c.branch !== currentBranch
|
|
16351
16389
|
);
|
|
16352
16390
|
const outcomes = [];
|
|
@@ -16355,9 +16393,9 @@ async function reapAll(cwd, branches, worktrees, currentBranch, deps) {
|
|
|
16355
16393
|
}
|
|
16356
16394
|
return outcomes;
|
|
16357
16395
|
}
|
|
16358
|
-
function findBareBranchCandidates(branches, worktrees) {
|
|
16396
|
+
function findBareBranchCandidates(branches, worktrees, remoteBranchNames) {
|
|
16359
16397
|
const worktreeBranches = new Set(worktrees.map((w) => w.branch));
|
|
16360
|
-
return branches.filter((b) => b
|
|
16398
|
+
return branches.filter((b) => hasProvenGoneRemote(b, remoteBranchNames) && !worktreeBranches.has(b.name)).map((b) => ({ branch: b.name }));
|
|
16361
16399
|
}
|
|
16362
16400
|
async function reapBareBranch(cwd, candidate, deps) {
|
|
16363
16401
|
const { git, github } = deps;
|
|
@@ -16386,8 +16424,8 @@ async function reapBareBranch(cwd, candidate, deps) {
|
|
|
16386
16424
|
const branchDeleted = await git.deleteBranch(cwd, candidate.branch);
|
|
16387
16425
|
return { candidate, verdict, branchDeleted };
|
|
16388
16426
|
}
|
|
16389
|
-
async function reapAllBareBranches(cwd, branches, worktrees, currentBranch, deps) {
|
|
16390
|
-
const candidates = findBareBranchCandidates(branches, worktrees).filter(
|
|
16427
|
+
async function reapAllBareBranches(cwd, branches, worktrees, currentBranch, deps, remoteBranchNames) {
|
|
16428
|
+
const candidates = findBareBranchCandidates(branches, worktrees, remoteBranchNames).filter(
|
|
16391
16429
|
(c) => c.branch !== currentBranch
|
|
16392
16430
|
);
|
|
16393
16431
|
const outcomes = [];
|
|
@@ -16464,10 +16502,19 @@ async function gatherRepoFacts(options, deps) {
|
|
|
16464
16502
|
};
|
|
16465
16503
|
}
|
|
16466
16504
|
async function runDoctorFix(cwd, facts, deps = { git: new GitAdapter(), github: new GithubCliAdapter() }) {
|
|
16467
|
-
|
|
16505
|
+
const remoteBranchNames = await deps.git.listRemoteBranchNames(cwd);
|
|
16506
|
+
return reapAll(cwd, facts.branches, facts.worktrees, facts.currentBranch, deps, remoteBranchNames);
|
|
16468
16507
|
}
|
|
16469
16508
|
async function runDoctorFixBranches(cwd, facts, deps = { git: new GitAdapter(), github: new GithubCliAdapter() }) {
|
|
16470
|
-
|
|
16509
|
+
const remoteBranchNames = await deps.git.listRemoteBranchNames(cwd);
|
|
16510
|
+
return reapAllBareBranches(
|
|
16511
|
+
cwd,
|
|
16512
|
+
facts.branches,
|
|
16513
|
+
facts.worktrees,
|
|
16514
|
+
facts.currentBranch,
|
|
16515
|
+
deps,
|
|
16516
|
+
remoteBranchNames
|
|
16517
|
+
);
|
|
16471
16518
|
}
|
|
16472
16519
|
function readLocalCoreVersion(cwd) {
|
|
16473
16520
|
const path = join69(cwd, INSTANCE_CORE_FILE);
|