pi-git-commit 1.0.7 → 1.0.8
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/README.md +1 -1
- package/index.ts +40 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@ The guard parses the command into segments (pipelines, `&&`, `||`, `;`, `&`, com
|
|
|
67
67
|
A blocked command returns:
|
|
68
68
|
|
|
69
69
|
```text
|
|
70
|
-
Mutative git commands are blocked.
|
|
70
|
+
Mutative git commands are blocked. Ask the user to run /toggle-allow-git to allow them for this session.
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
## Troubleshooting
|
package/index.ts
CHANGED
|
@@ -15,10 +15,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
15
15
|
const GIT_META_OPTS = new Set(["--help", "-h", "--version"]);
|
|
16
16
|
const GIT_OPTS_BARE = new Set(["--bare", "-p", "--paginate", "--no-pager", "--no-replace-objects", "--literal-pathspecs", "--glob-pathspecs", "--noglob-pathspecs", "--icase-pathspecs", "--no-optional-locks", "--html-path", "--man-path", "--info-path"]);
|
|
17
17
|
const GIT_OPTS_WITH_ARG = new Set(["-c", "--git-dir", "--work-tree", "--namespace", "--exec-path", "--super-prefix", "--shallow-file", "--template", "--upload-pack"]);
|
|
18
|
+
const GIT_TAG_VALUE_FLAGS = new Set(["--sort", "--format", "-v"]);
|
|
19
|
+
const GIT_TAG_LIST_FORCING = new Set(["--contains", "--no-contains", "--merged", "--no-merged", "--points-at"]);
|
|
18
20
|
|
|
19
21
|
const blockAll = () => true;
|
|
20
22
|
const hasAny = (args: string[], values: string[]) => values.some((value) => args.includes(value));
|
|
21
|
-
const
|
|
23
|
+
const blockUnlessAny = (values: string[]) => (args: string[]) => !hasAny(args, values);
|
|
22
24
|
const hasShortFlag = (args: string[], flags: string) => args.some((arg) => arg.startsWith("-") && !arg.startsWith("--") && arg.length > 1 && [...arg.slice(1)].some((flag) => flags.includes(flag)));
|
|
23
25
|
|
|
24
26
|
const GIT_RULES: Record<string, (args: string[]) => boolean> = {
|
|
@@ -64,15 +66,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
64
66
|
if (hasAny(args, ["--add", "--unset", "--unset-all", "--replace-all", "--remove-section", "--rename-section", "--edit", "-e"])) return true;
|
|
65
67
|
return !hasAny(args, ["--list", "-l", "--get", "--get-all", "--get-regexp", "--show-origin", "--show-scope"]);
|
|
66
68
|
},
|
|
67
|
-
remote: (args) =>
|
|
68
|
-
apply:
|
|
69
|
-
notes:
|
|
70
|
-
lfs:
|
|
71
|
-
"sparse-checkout":
|
|
72
|
-
stash:
|
|
73
|
-
submodule:
|
|
74
|
-
worktree:
|
|
75
|
-
reflog: (args) =>
|
|
69
|
+
remote: (args) => args.length > 0 && blockUnlessAny(["-v", "show", "get-url"])(args),
|
|
70
|
+
apply: blockUnlessAny(["--check", "--stat"]),
|
|
71
|
+
notes: blockUnlessAny(["list", "show"]),
|
|
72
|
+
lfs: blockUnlessAny(["ls-files", "status"]),
|
|
73
|
+
"sparse-checkout": blockUnlessAny(["list"]),
|
|
74
|
+
stash: blockUnlessAny(["list", "show"]),
|
|
75
|
+
submodule: blockUnlessAny(["status", "init", "summary"]),
|
|
76
|
+
worktree: blockUnlessAny(["list"]),
|
|
77
|
+
reflog: (args) => args.length > 0 && blockUnlessAny(["show"])(args),
|
|
76
78
|
branch: (args) => {
|
|
77
79
|
if (args.includes("--")) return true;
|
|
78
80
|
if (hasAny(args, ["--delete", "--move", "--copy", "--force", "--track", "--prune", "--unset-upstream", "--edit-description"]) || args.some((arg) => arg.startsWith("--set-upstream-to"))) return true;
|
|
@@ -82,9 +84,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
82
84
|
},
|
|
83
85
|
tag: (args) => {
|
|
84
86
|
if (args.length === 0) return false;
|
|
87
|
+
if (hasAny(args, ["--delete", "--annotate", "--force", "--sign", "--edit", "--cleanup"]) || hasShortFlag(args, "damfsueF")) return true;
|
|
85
88
|
const first = args[0];
|
|
86
89
|
if (first === "-l" || first === "--list" || first.startsWith("-n")) return false;
|
|
87
|
-
|
|
90
|
+
const readOnlyFlags = ["--contains", "--no-contains", "--merged", "--no-merged", "--points-at", "--sort", "--format", "--column", "--no-column", "--color", "--ignore-case", "--verbose", "-i", "-v"];
|
|
91
|
+
if (!readOnlyFlags.some((flag) => first.startsWith(flag))) return true;
|
|
92
|
+
if ([...GIT_TAG_LIST_FORCING].some((flag) => args.some((arg) => arg === flag || arg.startsWith(`${flag}=`)))) return false;
|
|
93
|
+
return args.some((arg, index) => index > 0 && !arg.startsWith("-") && !GIT_TAG_VALUE_FLAGS.has(args[index - 1]));
|
|
88
94
|
},
|
|
89
95
|
checkout: (args) => {
|
|
90
96
|
if (args[0] !== "--") return true;
|
|
@@ -98,7 +104,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
98
104
|
const maskHeredocBodies = (command: string): string => {
|
|
99
105
|
let masked = "";
|
|
100
106
|
let cursor = 0;
|
|
101
|
-
const heredocRe = /<<-?\s
|
|
107
|
+
const heredocRe = /<<-?\s*\\?['"]?([A-Za-z_][A-Za-z0-9_]*)['"]?/g;
|
|
102
108
|
let match: RegExpExecArray | null;
|
|
103
109
|
while ((match = heredocRe.exec(command)) !== null) {
|
|
104
110
|
const current = match;
|
|
@@ -161,6 +167,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
161
167
|
let subcommandIndex = -1;
|
|
162
168
|
for (let i = 0; i < tokens.length; i++) {
|
|
163
169
|
const token = tokens[i];
|
|
170
|
+
if (token === "git" || token === "git.exe") continue;
|
|
164
171
|
if (GIT_META_OPTS.has(token)) return false;
|
|
165
172
|
if (GIT_OPTS_BARE.has(token)) continue;
|
|
166
173
|
if (GIT_OPTS_WITH_ARG.has(token)) {
|
|
@@ -205,7 +212,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
205
212
|
if (typeof command !== "string") return undefined;
|
|
206
213
|
const trimmed = command.trim();
|
|
207
214
|
if (gitBlocked && containsBlockedGitCommand(trimmed)) {
|
|
208
|
-
return { block: true, reason: "Mutative git commands are blocked.
|
|
215
|
+
return { block: true, reason: "Mutative git commands are blocked. Ask the user to run /toggle-allow-git to allow them for this session." };
|
|
209
216
|
}
|
|
210
217
|
return undefined;
|
|
211
218
|
});
|
|
@@ -235,6 +242,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
235
242
|
return rest;
|
|
236
243
|
};
|
|
237
244
|
|
|
245
|
+
const toolError = (text: string) => ({ content: [{ type: "text" as const, text }], details: {}, isError: true });
|
|
246
|
+
|
|
238
247
|
pi.registerTool({
|
|
239
248
|
name: "git_commit",
|
|
240
249
|
label: "Git Commit",
|
|
@@ -257,17 +266,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
257
266
|
const { type, message } = params;
|
|
258
267
|
const description = stripLeadingCommitType(type, message);
|
|
259
268
|
if (!description) {
|
|
260
|
-
return
|
|
269
|
+
return toolError("Commit message must not be empty.");
|
|
261
270
|
}
|
|
262
271
|
const fullMessage = `${type}: ${description}`;
|
|
263
272
|
const addResult = await pi.exec("git", ["add", "."], { signal });
|
|
264
273
|
if (addResult.code !== 0) {
|
|
265
|
-
return
|
|
274
|
+
return toolError(`Staging failed: ${addResult.stderr}`);
|
|
266
275
|
}
|
|
267
276
|
|
|
268
277
|
const result = await pi.exec("git", ["commit", "-m", fullMessage], { signal });
|
|
269
278
|
if (result.code !== 0) {
|
|
270
|
-
return
|
|
279
|
+
return toolError(`Commit failed: ${result.stderr}`);
|
|
271
280
|
}
|
|
272
281
|
committed = true;
|
|
273
282
|
pi.sendMessage(
|
|
@@ -316,26 +325,28 @@ export default function (pi: ExtensionAPI) {
|
|
|
316
325
|
}
|
|
317
326
|
commitFlowActive = true;
|
|
318
327
|
|
|
328
|
+
const execGit = async (label: string, args: string[]) => {
|
|
329
|
+
const result = await pi.exec("git", args);
|
|
330
|
+
if (result.code !== 0) {
|
|
331
|
+
ctx.ui.notify(`${label} failed: ${result.stderr}`, "error");
|
|
332
|
+
commitFlowActive = false;
|
|
333
|
+
return undefined;
|
|
334
|
+
}
|
|
335
|
+
return result;
|
|
336
|
+
};
|
|
337
|
+
|
|
319
338
|
try {
|
|
320
339
|
await ctx.ui.setWorkingMessage("Waiting for queued messages to complete...");
|
|
321
340
|
await ctx.waitForIdle();
|
|
322
341
|
if (!commitFlowActive) return;
|
|
323
342
|
|
|
324
343
|
await ctx.ui.setWorkingMessage("Staging files...");
|
|
325
|
-
const addResult = await
|
|
326
|
-
if (addResult
|
|
327
|
-
ctx.ui.notify(`git add failed: ${addResult.stderr}`, "error");
|
|
328
|
-
commitFlowActive = false;
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
344
|
+
const addResult = await execGit("git add", ["add", "."]);
|
|
345
|
+
if (!addResult || !commitFlowActive) return;
|
|
331
346
|
|
|
332
347
|
await ctx.ui.setWorkingMessage("Getting diff...");
|
|
333
|
-
const diffResult = await
|
|
334
|
-
if (diffResult
|
|
335
|
-
ctx.ui.notify(`git diff failed: ${diffResult.stderr}`, "error");
|
|
336
|
-
commitFlowActive = false;
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
348
|
+
const diffResult = await execGit("git diff", ["diff", "--staged"]);
|
|
349
|
+
if (!diffResult || !commitFlowActive) return;
|
|
339
350
|
|
|
340
351
|
if (!diffResult.stdout.trim()) {
|
|
341
352
|
ctx.ui.notify("Nothing to commit (empty diff). Stage files first.", "warning");
|
|
@@ -343,7 +354,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
343
354
|
return;
|
|
344
355
|
}
|
|
345
356
|
|
|
346
|
-
const diff = diffResult.stdout
|
|
357
|
+
const diff = diffResult.stdout;
|
|
347
358
|
|
|
348
359
|
const statResult = await pi.exec("git", ["diff", "--staged", "--stat"]);
|
|
349
360
|
if (!commitFlowActive) return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-git-commit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Pi extension: block mutative git commands in bash and provide a git_commit tool plus /commit, /stop-commit and /toggle-allow-git commands",
|
|
6
6
|
"main": "index.ts",
|