@pmelab/gtd 26.0.0 → 26.1.0
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/.claude-plugin/plugin.json +1 -1
- package/README.md +5 -0
- package/dist/flows.mjs +15 -1
- package/dist/gtd.bundle.mjs +134 -18
- package/dist/testkit.mjs +284 -55
- package/package.json +1 -1
- package/src/cli/Cli.test.ts +3 -3
- package/src/cli/History.test.ts +2 -2
- package/src/cli/Program.test.ts +10 -12
- package/src/edge/Edge.test.ts +113 -16
- package/src/edge/Edge.ts +22 -6
- package/src/emit/GitScript.test.ts +65 -2
- package/src/emit/GitScript.ts +34 -1
- package/src/emit/index.ts +1 -0
- package/src/flags/index.ts +0 -1
- package/src/flows/runtime.test.ts +33 -0
- package/src/flows/runtime.ts +31 -0
- package/src/platform/RetainedHistory.test.ts +9 -0
- package/src/platform/RetainedHistory.ts +3 -0
- package/src/platform/Workspace.test.ts +1 -1
- package/src/platform/index.ts +1 -0
- package/src/replay/RefName.test.ts +40 -0
- package/src/replay/RefName.ts +19 -0
- package/src/replay/Replay.test.ts +193 -2
- package/src/replay/Replay.ts +51 -6
- package/src/replay/Trailers.test.ts +26 -0
- package/src/replay/Trailers.ts +8 -0
- package/src/replay/index.ts +1 -0
- package/src/step/LandStep.ts +1 -0
- package/src/step/RepoSnapshot.ts +2 -0
- package/src/step/planStart.test.ts +5 -5
- package/src/step/planStep.test.ts +53 -0
- package/src/step/planStep.ts +7 -1
- package/src/testkit/EmittedScriptRecognizer.test.ts +57 -29
- package/src/testkit/EmittedScriptRecognizer.ts +26 -4
- package/src/testkit/EmittedScripts.test.ts +182 -0
- package/src/testkit/FakeGitOperations.ts +8 -2
- package/src/testkit/GitTiers.ts +103 -24
- package/src/testkit/InMemRepo.ts +57 -12
- package/src/testkit/TestRepo.ts +2 -2
- package/src/workflow/Workflow.ts +1 -0
- package/src/workflow/discovery.test.ts +1 -1
- package/src/workflows/shared/text.fixture.ts +1 -0
package/README.md
CHANGED
|
@@ -576,6 +576,11 @@ git config --add remote.origin.fetch 'refs/gtd/history/*:refs/gtd/history/*'
|
|
|
576
576
|
The second line makes every plain `git fetch` include them. It has no leading
|
|
577
577
|
`+`, so a conflicting local ref fails instead of being overwritten.
|
|
578
578
|
|
|
579
|
+
A step declared with `keep: true` files its turn under `refs/gtd/kept/` instead
|
|
580
|
+
of on the branch (see [Kept tips](docs/configuration.md#kept-tips)). Those refs
|
|
581
|
+
are not process history: `gtd history` does not list them, and the
|
|
582
|
+
`refs/gtd/history/*` refspecs above neither push nor fetch them.
|
|
583
|
+
|
|
579
584
|
## Entrypoints
|
|
580
585
|
|
|
581
586
|
gtd is one package, `@pmelab/gtd`, and every entrypoint shares its version and
|
package/dist/flows.mjs
CHANGED
|
@@ -109,6 +109,19 @@ const changesSince = (hash, pattern) => {
|
|
|
109
109
|
const all = context.changesSince(hash);
|
|
110
110
|
return toChanges(pattern === void 0 ? all : all.filter((c) => context.matches(c.path, pattern)));
|
|
111
111
|
};
|
|
112
|
+
/**
|
|
113
|
+
* The latest kept tip of step `name` (scoped from here, same as `agent()`) in
|
|
114
|
+
* this episode, or `undefined` when none.
|
|
115
|
+
*/
|
|
116
|
+
const kept = (name) => {
|
|
117
|
+
const context = ctx();
|
|
118
|
+
if (typeof context.kept !== "function") throw new Error("gtd: kept() needs a newer gtd — upgrade the gtd that runs it");
|
|
119
|
+
const tip = context.kept(name);
|
|
120
|
+
return tip === void 0 ? void 0 : {
|
|
121
|
+
hash: tip.hash,
|
|
122
|
+
changes: toChanges(tip.changes)
|
|
123
|
+
};
|
|
124
|
+
};
|
|
112
125
|
/** The commit the process stands on at this point of the flow. */
|
|
113
126
|
const head = () => ctx().head();
|
|
114
127
|
/** The process's diff base: the commit before it began, or the base `gtd --workflow` fixed. */
|
|
@@ -502,6 +515,7 @@ var flows_exports = /* @__PURE__ */ __exportAll({
|
|
|
502
515
|
installContext: () => installContext,
|
|
503
516
|
isFactoryWorkflow: () => isFactoryWorkflow,
|
|
504
517
|
judge: () => judge,
|
|
518
|
+
kept: () => kept,
|
|
505
519
|
moveScript: () => moveScript,
|
|
506
520
|
numeric: () => numeric,
|
|
507
521
|
openQuestions: () => openQuestions,
|
|
@@ -530,4 +544,4 @@ var flows_exports = /* @__PURE__ */ __exportAll({
|
|
|
530
544
|
wrote: () => wrote
|
|
531
545
|
});
|
|
532
546
|
//#endregion
|
|
533
|
-
export { FLOWS_PROTOCOL, FlowsProtocolError, accessFor, agent, answered, changes, changesSince, check, checkScript, codeChanges, codeThreads, configProblems, configure, defineConfig, defineWorkflow, env, glob, hasThreadFor, head, human, installContext, isFactoryWorkflow, judge, moveScript, numeric, openQuestions, protocolMismatch, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restart, restoreScript, revertScript, run, scope, sectionBodies, sections, showScript, skillsFor, start, flows_exports as t, threads, vars, wrote };
|
|
547
|
+
export { FLOWS_PROTOCOL, FlowsProtocolError, accessFor, agent, answered, changes, changesSince, check, checkScript, codeChanges, codeThreads, configProblems, configure, defineConfig, defineWorkflow, env, glob, hasThreadFor, head, human, installContext, isFactoryWorkflow, judge, kept, moveScript, numeric, openQuestions, protocolMismatch, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restart, restoreScript, revertScript, run, scope, sectionBodies, sections, showScript, skillsFor, start, flows_exports as t, threads, vars, wrote };
|
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -20943,6 +20943,8 @@ const HISTORY_REF = "refs/worktree/gtd/history";
|
|
|
20943
20943
|
/** One ref per finished process, kept past the branch's deletion; distinct from the per-worktree `HISTORY_REF`. */
|
|
20944
20944
|
const PROCESS_HISTORY_PREFIX = "refs/gtd/history/";
|
|
20945
20945
|
const processHistoryRef = (hash) => `${PROCESS_HISTORY_PREFIX}${hash}`;
|
|
20946
|
+
/** Directory of a step's turns set aside by `keep: true`, each at `<this>/<kept hash>`; own namespace, so history list/show/fetch never see it. */
|
|
20947
|
+
const keptRef = (entry, step) => `refs/gtd/kept/${entry}/${step}`;
|
|
20946
20948
|
const readRetainedHistory = (git) => git.readRefOption(HISTORY_REF);
|
|
20947
20949
|
/**
|
|
20948
20950
|
* Whether resetting to `tipHash` is safe: HEAD must be an ancestor of
|
|
@@ -21715,6 +21717,19 @@ const changesSince = (hash, pattern) => {
|
|
|
21715
21717
|
const all = context.changesSince(hash);
|
|
21716
21718
|
return toChanges(pattern === void 0 ? all : all.filter((c) => context.matches(c.path, pattern)));
|
|
21717
21719
|
};
|
|
21720
|
+
/**
|
|
21721
|
+
* The latest kept tip of step `name` (scoped from here, same as `agent()`) in
|
|
21722
|
+
* this episode, or `undefined` when none.
|
|
21723
|
+
*/
|
|
21724
|
+
const kept = (name) => {
|
|
21725
|
+
const context = ctx();
|
|
21726
|
+
if (typeof context.kept !== "function") throw new Error("gtd: kept() needs a newer gtd — upgrade the gtd that runs it");
|
|
21727
|
+
const tip = context.kept(name);
|
|
21728
|
+
return tip === void 0 ? void 0 : {
|
|
21729
|
+
hash: tip.hash,
|
|
21730
|
+
changes: toChanges(tip.changes)
|
|
21731
|
+
};
|
|
21732
|
+
};
|
|
21718
21733
|
/** The commit the process stands on at this point of the flow. */
|
|
21719
21734
|
const head = () => ctx().head();
|
|
21720
21735
|
/** The process's diff base: the commit before it began, or the base `gtd --workflow` fixed. */
|
|
@@ -22108,6 +22123,7 @@ var flows_exports = /* @__PURE__ */ __exportAll({
|
|
|
22108
22123
|
installContext: () => installContext,
|
|
22109
22124
|
isFactoryWorkflow: () => isFactoryWorkflow,
|
|
22110
22125
|
judge: () => judge,
|
|
22126
|
+
kept: () => kept,
|
|
22111
22127
|
moveScript: () => moveScript,
|
|
22112
22128
|
numeric: () => numeric,
|
|
22113
22129
|
openQuestions: () => openQuestions,
|
|
@@ -37491,8 +37507,24 @@ var init_wire = __esmMin((() => {
|
|
|
37491
37507
|
init_Glob();
|
|
37492
37508
|
}));
|
|
37493
37509
|
//#endregion
|
|
37494
|
-
//#region src/replay/
|
|
37510
|
+
//#region src/replay/RefName.ts
|
|
37495
37511
|
init_wire();
|
|
37512
|
+
const FORBIDDEN = /[\u0000- \u007f~^:?*[\\]/;
|
|
37513
|
+
/** Why `name` cannot be (part of) a git ref name, or `undefined` when it can. */
|
|
37514
|
+
const refNameFault = (name) => {
|
|
37515
|
+
if (name === "") return "it is empty";
|
|
37516
|
+
if (FORBIDDEN.test(name)) return "it contains a space, control character or one of ~^:?*[\\";
|
|
37517
|
+
if (name.includes("..")) return "it contains \"..\"";
|
|
37518
|
+
if (name.includes("@{")) return "it contains \"@{\"";
|
|
37519
|
+
if (name.endsWith(".")) return "it ends with \".\"";
|
|
37520
|
+
for (const part of name.split("/")) {
|
|
37521
|
+
if (part === "") return "it has an empty \"/\" component";
|
|
37522
|
+
if (part.startsWith(".")) return "a component starts with \".\"";
|
|
37523
|
+
if (part.endsWith(".lock")) return "a component ends with \".lock\"";
|
|
37524
|
+
}
|
|
37525
|
+
};
|
|
37526
|
+
//#endregion
|
|
37527
|
+
//#region src/replay/Tree.ts
|
|
37496
37528
|
const treeFromRecord = (files) => ({
|
|
37497
37529
|
paths: () => Object.keys(files).sort(),
|
|
37498
37530
|
read: (path) => Object.hasOwn(files, path) ? files[path] : void 0
|
|
@@ -37586,6 +37618,7 @@ const decodePath = (raw) => {
|
|
|
37586
37618
|
return typeof path === "string" && path !== "" && !CONTROL_CHARACTER$1.test(path) ? path : void 0;
|
|
37587
37619
|
};
|
|
37588
37620
|
const FORMAT_RE = /^[1-9][0-9]*$/;
|
|
37621
|
+
const HASH_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
37589
37622
|
const TRAILER_READERS = {
|
|
37590
37623
|
"Gtd-Step": (value, into) => {
|
|
37591
37624
|
into.step ??= parseStepId(value);
|
|
@@ -37615,6 +37648,9 @@ const TRAILER_READERS = {
|
|
|
37615
37648
|
"Gtd-Version": (value, into) => {
|
|
37616
37649
|
if (value !== "") into.version ??= value;
|
|
37617
37650
|
},
|
|
37651
|
+
"Gtd-Kept": (value, into) => {
|
|
37652
|
+
if (HASH_RE.test(value)) into.kept ??= value;
|
|
37653
|
+
},
|
|
37618
37654
|
"Gtd-Format": (value, into) => {
|
|
37619
37655
|
if (FORMAT_RE.test(value)) into.format ??= Number(value);
|
|
37620
37656
|
},
|
|
@@ -37635,6 +37671,7 @@ const parseCommitMessage = (message) => {
|
|
|
37635
37671
|
reviewBase: void 0,
|
|
37636
37672
|
branch: void 0,
|
|
37637
37673
|
version: void 0,
|
|
37674
|
+
kept: void 0,
|
|
37638
37675
|
judge: [],
|
|
37639
37676
|
vars: {},
|
|
37640
37677
|
config: {},
|
|
@@ -37749,7 +37786,8 @@ const KNOWN_OPTIONS = {
|
|
|
37749
37786
|
...STEERING_OPTIONS,
|
|
37750
37787
|
"model",
|
|
37751
37788
|
"system",
|
|
37752
|
-
"allowEmpty"
|
|
37789
|
+
"allowEmpty",
|
|
37790
|
+
"keep"
|
|
37753
37791
|
]),
|
|
37754
37792
|
human: /* @__PURE__ */ new Set([
|
|
37755
37793
|
...STEERING_OPTIONS,
|
|
@@ -37781,6 +37819,7 @@ const replay = async (input) => {
|
|
|
37781
37819
|
const scopeCallIds = [];
|
|
37782
37820
|
let scopeCallCount = 0;
|
|
37783
37821
|
const trace = [];
|
|
37822
|
+
const keptTips = /* @__PURE__ */ new Map();
|
|
37784
37823
|
let landed;
|
|
37785
37824
|
let outcome;
|
|
37786
37825
|
let signal;
|
|
@@ -37940,6 +37979,12 @@ const replay = async (input) => {
|
|
|
37940
37979
|
}
|
|
37941
37980
|
cursor++;
|
|
37942
37981
|
advance(commit);
|
|
37982
|
+
if (commit.parsed.kept !== void 0) if (commit.kept === void 0) keptTips.delete(step.name);
|
|
37983
|
+
else keptTips.set(step.name, {
|
|
37984
|
+
hash: commit.kept.hash,
|
|
37985
|
+
before: commit.tree,
|
|
37986
|
+
after: commit.kept.tree
|
|
37987
|
+
});
|
|
37943
37988
|
const to = commit.parsed.parsed?.to;
|
|
37944
37989
|
if (to !== void 0) expectedNext = {
|
|
37945
37990
|
name: to,
|
|
@@ -37947,6 +37992,21 @@ const replay = async (input) => {
|
|
|
37947
37992
|
};
|
|
37948
37993
|
return replyFor(step.request, commit.parsed.judge);
|
|
37949
37994
|
};
|
|
37995
|
+
const landPending = (step, pending) => {
|
|
37996
|
+
pendingUsed = true;
|
|
37997
|
+
landed = step;
|
|
37998
|
+
const keeping = step.request.kind === "agent" && step.request.options.keep === true;
|
|
37999
|
+
if (keeping) keptTips.set(step.name, {
|
|
38000
|
+
hash: "",
|
|
38001
|
+
before: position.tree,
|
|
38002
|
+
after: pending.tree
|
|
38003
|
+
});
|
|
38004
|
+
advance({
|
|
38005
|
+
hash: "",
|
|
38006
|
+
tree: keeping ? position.tree : pending.tree
|
|
38007
|
+
});
|
|
38008
|
+
return replyFor(step.request, pending.verdicts ?? []);
|
|
38009
|
+
};
|
|
37950
38010
|
const answer = (step) => {
|
|
37951
38011
|
while (cursor < commits.length && isAttemptAt(commits[cursor], step.name)) cursor++;
|
|
37952
38012
|
const commit = commits[cursor];
|
|
@@ -37957,17 +38017,18 @@ const replay = async (input) => {
|
|
|
37957
38017
|
trace,
|
|
37958
38018
|
landed
|
|
37959
38019
|
});
|
|
37960
|
-
|
|
37961
|
-
landed = step;
|
|
37962
|
-
advance({
|
|
37963
|
-
hash: "",
|
|
37964
|
-
tree: input.pending.tree
|
|
37965
|
-
});
|
|
37966
|
-
return replyFor(step.request, input.pending.verdicts ?? []);
|
|
38020
|
+
return landPending(step, input.pending);
|
|
37967
38021
|
};
|
|
37968
38022
|
const requestFault = (request) => {
|
|
37969
38023
|
if (request.kind === "run" && typeof request.body !== "string") return `gtd: step "${scoped(request.name)}": a run() body is a shell script string — decide in flow code, then render the script (see checkScript and friends)`;
|
|
37970
|
-
|
|
38024
|
+
if (request.kind !== "agent") return void 0;
|
|
38025
|
+
const name = scoped(request.name);
|
|
38026
|
+
if (request.options.keep === true) {
|
|
38027
|
+
if (request.options.allowEmpty === true) return `gtd: step "${name}": keep: true cannot be combined with allowEmpty — a turn that changes nothing has nothing to keep`;
|
|
38028
|
+
const refFault = name.includes("/") ? "it contains \"/\"" : refNameFault(name);
|
|
38029
|
+
if (refFault !== void 0) return `gtd: step "${name}": keep: true files the turn under a git ref named after the step, but ${refFault}`;
|
|
38030
|
+
}
|
|
38031
|
+
return scopeAccessFault();
|
|
37971
38032
|
};
|
|
37972
38033
|
const handleStep = async (request) => {
|
|
37973
38034
|
if (outcome !== void 0) throw new Stop();
|
|
@@ -38050,6 +38111,13 @@ const replay = async (input) => {
|
|
|
38050
38111
|
faults: findings.filter((f) => f.message.startsWith(`Code thread at ${c.path}:${t.line + 1}: `)).map((f) => f.message)
|
|
38051
38112
|
}));
|
|
38052
38113
|
}),
|
|
38114
|
+
kept: (name) => {
|
|
38115
|
+
const tip = keptTips.get(scoped(name));
|
|
38116
|
+
return tip === void 0 ? void 0 : {
|
|
38117
|
+
hash: tip.hash,
|
|
38118
|
+
changes: changesBetween(tip.before, tip.after)
|
|
38119
|
+
};
|
|
38120
|
+
},
|
|
38053
38121
|
vars: input.vars,
|
|
38054
38122
|
env: input.env,
|
|
38055
38123
|
start: () => input.start,
|
|
@@ -39019,7 +39087,11 @@ const planStep = (snapshot, opts = {}) => {
|
|
|
39019
39087
|
...uncheckStep(snapshot),
|
|
39020
39088
|
{
|
|
39021
39089
|
kind: "gitWrite",
|
|
39022
|
-
write: {
|
|
39090
|
+
write: landing.keepTurn !== void 0 ? {
|
|
39091
|
+
kind: "keepTurn",
|
|
39092
|
+
ref: landing.keepTurn,
|
|
39093
|
+
message
|
|
39094
|
+
} : {
|
|
39023
39095
|
kind: "commitAll",
|
|
39024
39096
|
message
|
|
39025
39097
|
}
|
|
@@ -39499,6 +39571,7 @@ const promptDef = (common, request, skills, access) => ({
|
|
|
39499
39571
|
...optional$1("model", request.options.model),
|
|
39500
39572
|
...optional$1("system", request.options.system),
|
|
39501
39573
|
...optional$1("allowEmpty", request.options.allowEmpty),
|
|
39574
|
+
...optional$1("keep", request.options.keep),
|
|
39502
39575
|
...optional$1("skills", skills),
|
|
39503
39576
|
...optional$1("access", access)
|
|
39504
39577
|
});
|
|
@@ -39613,11 +39686,21 @@ const setupFor = (config, run, workspace, host, startOverrides = {}) => gen(func
|
|
|
39613
39686
|
hash: run.episode.base,
|
|
39614
39687
|
tree: commitTree(workspace, run.episode.base)
|
|
39615
39688
|
};
|
|
39616
|
-
const
|
|
39617
|
-
|
|
39618
|
-
|
|
39619
|
-
|
|
39620
|
-
|
|
39689
|
+
const git = yield* GitService;
|
|
39690
|
+
const episode = [];
|
|
39691
|
+
for (const c of run.episode.commits) {
|
|
39692
|
+
const tip = parseCommitMessage(c.message).kept;
|
|
39693
|
+
const exists = tip !== void 0 && isSome(yield* git.readRefOption(`${tip}^{commit}`));
|
|
39694
|
+
episode.push({
|
|
39695
|
+
hash: c.hash,
|
|
39696
|
+
message: c.message,
|
|
39697
|
+
tree: commitTree(workspace, c.hash),
|
|
39698
|
+
...exists ? { kept: {
|
|
39699
|
+
hash: tip,
|
|
39700
|
+
tree: commitTree(workspace, tip)
|
|
39701
|
+
} } : {}
|
|
39702
|
+
});
|
|
39703
|
+
}
|
|
39621
39704
|
let pendingMemo;
|
|
39622
39705
|
return {
|
|
39623
39706
|
resolved,
|
|
@@ -39793,6 +39876,7 @@ const closingTrailers = (branch) => ({
|
|
|
39793
39876
|
...isSome(branch) ? { branch: branch.value } : {}
|
|
39794
39877
|
});
|
|
39795
39878
|
const isSettledNoop = (rest, to) => rest.changes.length === 0 && rest.stepDef.kind === "script" && to === rest.state;
|
|
39879
|
+
const keptTurn = (rest) => rest.stepDef.keep === true && rest.changes.length > 0 ? { keepTurn: keptRef(rest.run.startHash, rest.state) } : {};
|
|
39796
39880
|
const landingTo = (rest, to, branch) => {
|
|
39797
39881
|
if (isSettledNoop(rest, to)) return {
|
|
39798
39882
|
kind: "noop",
|
|
@@ -39817,7 +39901,8 @@ const landingTo = (rest, to, branch) => {
|
|
|
39817
39901
|
} : {},
|
|
39818
39902
|
...finishes ? closingTrailers(branch) : {}
|
|
39819
39903
|
},
|
|
39820
|
-
...finishes ? { keepHistory: processHistoryRef(rest.run.startHash) } : {}
|
|
39904
|
+
...finishes ? { keepHistory: processHistoryRef(rest.run.startHash) } : {},
|
|
39905
|
+
...keptTurn(rest)
|
|
39821
39906
|
};
|
|
39822
39907
|
};
|
|
39823
39908
|
/**
|
|
@@ -39954,7 +40039,7 @@ const commitAllowEmpty = (message) => {
|
|
|
39954
40039
|
return [
|
|
39955
40040
|
`if ! out=$(git commit --allow-empty -m ${m} 2>&1); then`,
|
|
39956
40041
|
` case "$out" in`,
|
|
39957
|
-
` *"empty git commit"*) git commit --allow-empty --no-verify -m ${m} ;;`,
|
|
40042
|
+
` *"empty git commit"*) git commit --allow-empty --no-verify -m ${m} || exit 1 ;;`,
|
|
39958
40043
|
` *) printf '%s\\n' "$out" >&2; exit 1 ;;`,
|
|
39959
40044
|
` esac`,
|
|
39960
40045
|
`fi`
|
|
@@ -39964,6 +40049,36 @@ const commitAllowEmpty = (message) => {
|
|
|
39964
40049
|
const commitAll = (message) => `git add -A || exit 1\n${commitAllowEmpty(message)}`;
|
|
39965
40050
|
const mixedResetTo = (ref) => `git reset --mixed ${shellQuote$1(ref)}`;
|
|
39966
40051
|
const hardResetTo = (ref) => `git reset --hard ${shellQuote$1(ref)}`;
|
|
40052
|
+
/**
|
|
40053
|
+
* POSIX sh (runs under dash). The marker is a `commit-tree` over HEAD's own
|
|
40054
|
+
* tree, so no hook runs on it and its tree is the pre-turn tree by
|
|
40055
|
+
* construction; `commit-tree` ignores `commit.gpgSign`, hence the explicit
|
|
40056
|
+
* `-S`, read (and failing closed) before any object or ref is written; the
|
|
40057
|
+
* index is already staged by then, so an early exit leaves the turn staged.
|
|
40058
|
+
* The kept ref is deleted only after `reset -q` succeeds: it is then the
|
|
40059
|
+
* turn's only handle.
|
|
40060
|
+
* The branch moves before the final hard reset, so if that reset fails the
|
|
40061
|
+
* marker has landed and the turn is still in the working tree (kept ref
|
|
40062
|
+
* present); every other failure leaves the branch untouched.
|
|
40063
|
+
*/
|
|
40064
|
+
const keepTurn = (refPrefix, message) => {
|
|
40065
|
+
const keptRef = `${shellQuote$1(`${refPrefix}/`)}"$gtd_kept"`;
|
|
40066
|
+
return [
|
|
40067
|
+
`git add -A || exit 1`,
|
|
40068
|
+
`gtd_sign_cfg=$(git config --type=bool --default=false commit.gpgSign) || exit 1`,
|
|
40069
|
+
`if [ "$gtd_sign_cfg" = true ]; then gtd_sign=-S; else gtd_sign=--no-gpg-sign; fi`,
|
|
40070
|
+
`gtd_head=$(git rev-parse --verify HEAD) || exit 1`,
|
|
40071
|
+
`gtd_tree=$(git write-tree) || exit 1`,
|
|
40072
|
+
`gtd_kept=$(git commit-tree "$gtd_tree" -p "$gtd_head" -m ${shellQuote$1(message)}) || exit 1`,
|
|
40073
|
+
`git update-ref ${keptRef} "$gtd_kept" || exit 1`,
|
|
40074
|
+
`gtd_marker=$(printf '%s\\nGtd-Kept: %s' ${shellQuote$1(message)} "$gtd_kept")`,
|
|
40075
|
+
`if ! gtd_tip=$(git commit-tree "$gtd_sign" "$gtd_head^{tree}" -p "$gtd_head" -m "$gtd_marker") || ! git update-ref HEAD "$gtd_tip" "$gtd_head"; then`,
|
|
40076
|
+
` git reset -q && git update-ref -d ${keptRef}`,
|
|
40077
|
+
` exit 1`,
|
|
40078
|
+
`fi`,
|
|
40079
|
+
`git reset --hard HEAD`
|
|
40080
|
+
].join("\n");
|
|
40081
|
+
};
|
|
39967
40082
|
const updateRef = (ref, hash) => `git update-ref ${shellQuote$1(ref)} ${shellQuote$1(hash)}`;
|
|
39968
40083
|
/** `git update-ref -d <ref>` — idempotent: deleting a missing ref is already a no-op in real git. */
|
|
39969
40084
|
const deleteRef = (ref) => `git update-ref -d ${shellQuote$1(ref)}`;
|
|
@@ -39996,6 +40111,7 @@ const renderGitWrite = (write) => {
|
|
|
39996
40111
|
switch (write.kind) {
|
|
39997
40112
|
case "commitAll": return commitAll(write.message);
|
|
39998
40113
|
case "updateRef": return updateRef(write.ref, write.target);
|
|
40114
|
+
case "keepTurn": return keepTurn(write.ref, write.message);
|
|
39999
40115
|
}
|
|
40000
40116
|
};
|
|
40001
40117
|
/**
|