@tablation/crew 0.73.1 → 0.73.2
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/cli.js +80 -10
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3563,7 +3563,7 @@ function remoteBranchForIssue(cwd, remote, issueKey, templates = { name: "issue-
|
|
|
3563
3563
|
}
|
|
3564
3564
|
return null;
|
|
3565
3565
|
}
|
|
3566
|
-
function levelRemoteBranch(cwd, remote, branch) {
|
|
3566
|
+
function levelRemoteBranch(cwd, remote, branch, baseRef) {
|
|
3567
3567
|
const local = gitOk(cwd, ["rev-parse", "-q", "--verify", `refs/heads/${branch}`]);
|
|
3568
3568
|
if (!remoteBranchExists(cwd, remote, branch)) return { exists: local !== null };
|
|
3569
3569
|
if (local === null) {
|
|
@@ -3572,6 +3572,20 @@ function levelRemoteBranch(cwd, remote, branch) {
|
|
|
3572
3572
|
const remoteTip2 = gitOk(cwd, ["rev-parse", "-q", "--verify", `refs/remotes/${remote}/${branch}`]);
|
|
3573
3573
|
if (!remoteTip2 || remoteTip2 === local) return { exists: true };
|
|
3574
3574
|
if (!isAncestor(cwd, local, remoteTip2)) {
|
|
3575
|
+
if (baseRef && isAncestor(cwd, local, baseRef)) {
|
|
3576
|
+
const holder3 = worktrees(cwd).find((w) => w.branch === branch && !w.prunable);
|
|
3577
|
+
if (holder3) {
|
|
3578
|
+
if (gitOk(holder3.path, ["status", "--porcelain"])) {
|
|
3579
|
+
return { exists: true, problem: `${branch} holds only commits already on ${baseRef}, but the worktree ${holder3.path} that holds it has uncommitted changes` };
|
|
3580
|
+
}
|
|
3581
|
+
if (gitOk(holder3.path, ["reset", "--hard", "--quiet", remoteTip2]) === null) {
|
|
3582
|
+
return { exists: true, problem: `could not reset ${branch} to ${remote}/${branch} in ${holder3.path}` };
|
|
3583
|
+
}
|
|
3584
|
+
} else if (gitOk(cwd, ["branch", "-f", branch, remoteTip2]) === null) {
|
|
3585
|
+
return { exists: true, problem: `could not reset ${branch} to ${remote}/${branch}` };
|
|
3586
|
+
}
|
|
3587
|
+
return { exists: true, reset: `${branch} held only commits already on ${baseRef}; reset ${local.slice(0, 8)} -> ${remote}/${branch} at ${remoteTip2.slice(0, 8)}` };
|
|
3588
|
+
}
|
|
3575
3589
|
const diverged = !isAncestor(cwd, remoteTip2, local);
|
|
3576
3590
|
const holder2 = diverged ? worktrees(cwd).find((w) => w.branch === branch && !w.prunable) : void 0;
|
|
3577
3591
|
if (holder2) {
|
|
@@ -6483,11 +6497,12 @@ function locateBranchForTicket(dir, cfg, t, role, fetchedDirs) {
|
|
|
6483
6497
|
const found = remoteBranchForIssue(dir, remote, t.issue_id, branchTemplates(cfg, t), branchRenderer(t, role, cfg.branch.lowercase));
|
|
6484
6498
|
return found ? { branch: found, where: "remote" } : null;
|
|
6485
6499
|
}
|
|
6486
|
-
function materializeBranchForTicket(dir, cfg, t, role, onProblem) {
|
|
6500
|
+
function materializeBranchForTicket(dir, cfg, t, role, onProblem, onReset) {
|
|
6487
6501
|
const loc = locateBranchForTicket(dir, cfg, t, role);
|
|
6488
6502
|
if (!loc) return null;
|
|
6489
|
-
const r = levelRemoteBranch(dir, cfg.branch.remote, loc.branch);
|
|
6503
|
+
const r = levelRemoteBranch(dir, cfg.branch.remote, loc.branch, `${cfg.branch.remote}/${cfg.branch.base}`);
|
|
6490
6504
|
if (r.problem) onProblem?.(r.problem);
|
|
6505
|
+
if (r.reset) onReset?.(r.reset);
|
|
6491
6506
|
return loc.branch;
|
|
6492
6507
|
}
|
|
6493
6508
|
|
|
@@ -6774,10 +6789,10 @@ function applyRunWorktree(dir, cfg, plan) {
|
|
|
6774
6789
|
case "recut":
|
|
6775
6790
|
git(dir, ["worktree", "remove", "--force", plan.path]);
|
|
6776
6791
|
gitOk(dir, ["branch", "-D", plan.branch]);
|
|
6777
|
-
git(dir, ["worktree", "add", "-b", plan.branch, plan.path, plan.base]);
|
|
6792
|
+
git(dir, ["worktree", "add", "--no-track", "-b", plan.branch, plan.path, plan.base]);
|
|
6778
6793
|
return plan.path;
|
|
6779
6794
|
case "cut-fresh":
|
|
6780
|
-
git(dir, ["worktree", "add", "-b", plan.branch, plan.path, plan.base]);
|
|
6795
|
+
git(dir, ["worktree", "add", "--no-track", "-b", plan.branch, plan.path, plan.base]);
|
|
6781
6796
|
return plan.path;
|
|
6782
6797
|
}
|
|
6783
6798
|
}
|
|
@@ -6785,6 +6800,19 @@ function worktreeHoldsUnpushed(path, remote) {
|
|
|
6785
6800
|
const out = gitOk(path, ["for-each-ref", "--contains", "HEAD", "--count=1", "--format=%(refname)", `refs/remotes/${remote}/`]);
|
|
6786
6801
|
return out === null || out.trim() === "";
|
|
6787
6802
|
}
|
|
6803
|
+
function dropEmptyRunBranch(dir, cfg, branch) {
|
|
6804
|
+
const { remote, base } = cfg.branch;
|
|
6805
|
+
const baseRef = remoteConfigured(dir, remote) ? `${remote}/${base}` : base;
|
|
6806
|
+
if (gitOk(dir, ["rev-parse", "-q", "--verify", `refs/heads/${branch}`]) === null) return false;
|
|
6807
|
+
if (remoteConfigured(dir, remote) && remoteBranchExists(dir, remote, branch)) return false;
|
|
6808
|
+
if (countCommits(dir, `${baseRef}..${branch}`) !== 0) return false;
|
|
6809
|
+
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
6810
|
+
if (wt && existsSync14(wt.path)) {
|
|
6811
|
+
if (status(wt.path).length > 0) return false;
|
|
6812
|
+
if (gitOk(dir, ["worktree", "remove", wt.path]) === null) return false;
|
|
6813
|
+
}
|
|
6814
|
+
return gitOk(dir, ["branch", "-D", branch]) !== null;
|
|
6815
|
+
}
|
|
6788
6816
|
async function finishRunWorktree(dir, cfg, branch) {
|
|
6789
6817
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
6790
6818
|
if (!wt || !existsSync14(wt.path)) return { kind: "none" };
|
|
@@ -6953,6 +6981,12 @@ async function guardRunHandoff(route, ship, tracker, role, workingId, log, since
|
|
|
6953
6981
|
log
|
|
6954
6982
|
});
|
|
6955
6983
|
if (outcome.kind === "pushed") await retireRunWorktree(tracker, target.dir, cfg, t, role, seatId, ship.name, log);
|
|
6984
|
+
else {
|
|
6985
|
+
const branch = existingBranchForTicket(target.dir, cfg, t, role);
|
|
6986
|
+
if (branch && dropEmptyRunBranch(target.dir, cfg, branch)) {
|
|
6987
|
+
log.emit(`${t.issue_id}: dropped ${branch} \u2014 the run made no commits beyond ${cfg.branch.base}`, { ticket: t.issue_id, step: "handoff" });
|
|
6988
|
+
}
|
|
6989
|
+
}
|
|
6956
6990
|
if (since2) await guardRunStrays(route, ship, tracker, role, workingId, since2, log);
|
|
6957
6991
|
return outcome;
|
|
6958
6992
|
} catch (e) {
|
|
@@ -10584,6 +10618,19 @@ async function applyStrandedVerified(writer, s, contract, operatorId, memberId,
|
|
|
10584
10618
|
}
|
|
10585
10619
|
}
|
|
10586
10620
|
|
|
10621
|
+
// src/local-ref-skip.ts
|
|
10622
|
+
var LOCAL_REF_SKIP_MARKER = calloutHeader("WARNING", "Release Skipped");
|
|
10623
|
+
function localRefSkipComment(shipName2, problem) {
|
|
10624
|
+
return `${LOCAL_REF_SKIP_MARKER}
|
|
10625
|
+
> ${problem}
|
|
10626
|
+
|
|
10627
|
+
**This ticket is verified but was not released: the release ship's local branch disagrees with the remote.**
|
|
10628
|
+
|
|
10629
|
+
Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody verified and retries every cycle. If the local branch holds only commits already on the base, run \`git branch -f <branch> <remote>/<branch>\` in the release checkout (the crew now does this itself when it can); otherwise a person needs to reconcile the branch.
|
|
10630
|
+
`;
|
|
10631
|
+
}
|
|
10632
|
+
var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
|
|
10633
|
+
|
|
10587
10634
|
// src/connect-wizard.ts
|
|
10588
10635
|
import { delimiter, dirname as dirname10, join as join23 } from "path";
|
|
10589
10636
|
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync21, mkdirSync as mkdirSync13, writeFileSync as writeFileSync15 } from "fs";
|
|
@@ -11641,7 +11688,7 @@ function openReleaseCheckout(o) {
|
|
|
11641
11688
|
o.emit.emit(`release checkout: ${dir}`, { step: "release" });
|
|
11642
11689
|
const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
|
|
11643
11690
|
try {
|
|
11644
|
-
git(o.cwd, ["worktree", "add", "-q", "-b", branch, dir, `${remote}/${base}`]);
|
|
11691
|
+
git(o.cwd, ["worktree", "add", "-q", "--no-track", "-b", branch, dir, `${remote}/${base}`]);
|
|
11645
11692
|
} catch (e) {
|
|
11646
11693
|
close();
|
|
11647
11694
|
throw e;
|
|
@@ -11667,10 +11714,15 @@ function reportHeld(o, held) {
|
|
|
11667
11714
|
}
|
|
11668
11715
|
async function runRelease(o) {
|
|
11669
11716
|
const movedPast2 = [];
|
|
11670
|
-
const
|
|
11671
|
-
|
|
11717
|
+
const localRefSkips = [];
|
|
11718
|
+
const out = await runReleaseCycle(o, movedPast2, localRefSkips);
|
|
11719
|
+
return {
|
|
11720
|
+
...out,
|
|
11721
|
+
...movedPast2.length ? { movedPast: movedPast2 } : {},
|
|
11722
|
+
...localRefSkips.length ? { localRefSkips } : {}
|
|
11723
|
+
};
|
|
11672
11724
|
}
|
|
11673
|
-
async function runReleaseCycle(o, movedPast2) {
|
|
11725
|
+
async function runReleaseCycle(o, movedPast2, localRefSkips) {
|
|
11674
11726
|
const scoped = (msg) => o.scope ? `${o.scope}: ${msg}` : msg;
|
|
11675
11727
|
const tagPattern = o.repo.release.tagPattern ?? "v*";
|
|
11676
11728
|
o.emit.enter("release");
|
|
@@ -11682,8 +11734,9 @@ async function runReleaseCycle(o, movedPast2) {
|
|
|
11682
11734
|
if (t.status !== o.contract.statuses.verified) continue;
|
|
11683
11735
|
materializeBranchForTicket(o.cwd, o.repo, t, void 0, (problem) => {
|
|
11684
11736
|
skipped.add(t.id);
|
|
11737
|
+
localRefSkips.push({ ticket: t, problem });
|
|
11685
11738
|
o.emit.error(`not releasing ${t.issue_id} this cycle: ${problem}`, { ticket: t.issue_id });
|
|
11686
|
-
});
|
|
11739
|
+
}, (note) => o.emit.emit(`${t.issue_id}: ${note}`, { ticket: t.issue_id }));
|
|
11687
11740
|
}
|
|
11688
11741
|
if (skipped.size) releasable = o.tickets.filter((t) => !skipped.has(t.id));
|
|
11689
11742
|
}
|
|
@@ -11702,6 +11755,10 @@ async function runReleaseCycle(o, movedPast2) {
|
|
|
11702
11755
|
`not releasing ${t.issue_id}: local ${branch} at ${moved.tip.slice(0, 8)} is not on ${o.repo.branch.remote}/${branch} (${remoteTip2.slice(0, 8)}); not requeuing it`,
|
|
11703
11756
|
{ ticket: t.issue_id }
|
|
11704
11757
|
);
|
|
11758
|
+
localRefSkips.push({
|
|
11759
|
+
ticket: t,
|
|
11760
|
+
problem: `local ${branch} at ${moved.tip.slice(0, 8)} is not on ${o.repo.branch.remote}/${branch} (${remoteTip2.slice(0, 8)})`
|
|
11761
|
+
});
|
|
11705
11762
|
continue;
|
|
11706
11763
|
}
|
|
11707
11764
|
movedPast2.push({ ticket: t, verified: moved.verified, head: moved.tip, branch: pushedBranchForTicket(o.repo, t) });
|
|
@@ -17401,6 +17458,18 @@ ${changed2.map((c) => ` - ${c}
|
|
|
17401
17458
|
remit.warn(`could not note the red test gate on the board: ${e.message}`, { step: "merge" });
|
|
17402
17459
|
}
|
|
17403
17460
|
}
|
|
17461
|
+
if (outcome.localRefSkips?.length && !dryRun) {
|
|
17462
|
+
try {
|
|
17463
|
+
const reported = await tracker.events("local_ref_skip");
|
|
17464
|
+
const seat = releaseSeat(c);
|
|
17465
|
+
for (const s of outcome.localRefSkips) {
|
|
17466
|
+
if (localRefSkipReported(s.ticket, s.problem, reported)) continue;
|
|
17467
|
+
await tracker.postEvent(s.ticket.id, localRefSkipComment(cfg.ship.name, s.problem), seat, "local_ref_skip");
|
|
17468
|
+
}
|
|
17469
|
+
} catch (e) {
|
|
17470
|
+
remit.warn(`could not note the skipped release on the board: ${e.message}`, { step: "merge" });
|
|
17471
|
+
}
|
|
17472
|
+
}
|
|
17404
17473
|
if (outcome.externalClosures?.length) {
|
|
17405
17474
|
await applyExternalClosures(tracker, outcome.externalClosures, tracker.contract, remit, dryRun, target.dir);
|
|
17406
17475
|
}
|
|
@@ -19835,6 +19904,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19835
19904
|
continue;
|
|
19836
19905
|
}
|
|
19837
19906
|
if (!s2.upstream) continue;
|
|
19907
|
+
if (w.branch !== r.config.branch.base && s2.upstream === `${r.config.branch.remote}/${r.config.branch.base}`) continue;
|
|
19838
19908
|
tracked++;
|
|
19839
19909
|
if (s2.canFastForward) {
|
|
19840
19910
|
acted++;
|
package/package.json
CHANGED