@jwilger/pi-development-system 0.8.5 → 0.8.7
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/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { splitLines } from "./git-intent.ts";
|
|
2
|
-
import { resolveGit } from "./git-invocations.ts";
|
|
2
|
+
import { opaqueMentions, resolveGit } from "./git-invocations.ts";
|
|
3
|
+
import { stripHeredocs } from "./heredoc.ts";
|
|
3
4
|
|
|
4
5
|
export type CommitExtraction =
|
|
5
6
|
| { readonly kind: "not-commit" }
|
|
@@ -7,22 +8,6 @@ export type CommitExtraction =
|
|
|
7
8
|
| { readonly kind: "file"; readonly path: string }
|
|
8
9
|
| { readonly kind: "unknown"; readonly trailers: readonly string[] };
|
|
9
10
|
|
|
10
|
-
const HEREDOC =
|
|
11
|
-
/<<-?[ \t]*(['"]?)([A-Za-z_][A-Za-z0-9_]*)\1([^\n]*)\n([\s\S]*?)\n[ \t]*\2[ \t]*(?:\n|$)/g;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Heredoc bodies are data: take them out so their quotes cannot confuse line splitting. The rest of
|
|
15
|
-
* the opening line stays (`| git commit -F -`) with a `#HD<n>` marker naming the removed body.
|
|
16
|
-
*/
|
|
17
|
-
function stripHeredocs(command: string): { rest: string; bodies: string[] } {
|
|
18
|
-
const bodies: string[] = [];
|
|
19
|
-
const rest = command.replace(HEREDOC, (...m: unknown[]) => {
|
|
20
|
-
bodies.push(String(m[4]).trim());
|
|
21
|
-
return `${String(m[3])} #HD${bodies.length - 1}\n`;
|
|
22
|
-
});
|
|
23
|
-
return { rest, bodies };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
11
|
type Options = {
|
|
27
12
|
messages: string[];
|
|
28
13
|
trailers: string[];
|
|
@@ -135,7 +120,7 @@ export function extractCommits(command: string): CommitExtraction[] {
|
|
|
135
120
|
const found = resolution.invocations.flatMap((g) =>
|
|
136
121
|
g.sub === "commit" ? [extractOne(g.args, heredocFrom(lines, g.line, bodies))] : [],
|
|
137
122
|
);
|
|
138
|
-
const hidden = resolution
|
|
123
|
+
const hidden = opaqueMentions(resolution, "commit");
|
|
139
124
|
return hidden ? [...found, unknown()] : found;
|
|
140
125
|
}
|
|
141
126
|
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
substitutions,
|
|
14
14
|
WRAPPERS,
|
|
15
15
|
} from "./git-intent.ts";
|
|
16
|
+
import { stripHeredocs } from "./heredoc.ts";
|
|
16
17
|
|
|
17
18
|
export type GitInvocation = {
|
|
18
19
|
readonly sub: string;
|
|
@@ -26,12 +27,13 @@ export type GitInvocation = {
|
|
|
26
27
|
export type GitResolution = {
|
|
27
28
|
readonly invocations: readonly GitInvocation[];
|
|
28
29
|
/** True when git may run through something the parser cannot see into (xargs, `$CMD`, …). */
|
|
29
|
-
|
|
30
|
+
/** Words of segments that may run git in a way that cannot be read (`$GIT push`, `xargs git …`). */
|
|
31
|
+
readonly opaque: readonly string[];
|
|
30
32
|
};
|
|
31
33
|
|
|
32
34
|
type Collector = {
|
|
33
35
|
invocations: GitInvocation[];
|
|
34
|
-
opaque:
|
|
36
|
+
opaque: string[];
|
|
35
37
|
dir: string | undefined;
|
|
36
38
|
line: number;
|
|
37
39
|
};
|
|
@@ -57,7 +59,7 @@ function visitGit(rest: readonly string[], into: Collector): void {
|
|
|
57
59
|
}
|
|
58
60
|
const sub = rest[j];
|
|
59
61
|
if (sub === undefined) return;
|
|
60
|
-
if (sub.startsWith("$")) into.opaque
|
|
62
|
+
if (sub.startsWith("$")) into.opaque.push(...rest);
|
|
61
63
|
else {
|
|
62
64
|
const args = rest.slice(j + 1);
|
|
63
65
|
const line = into.line;
|
|
@@ -88,11 +90,12 @@ function visitSegment(tokens: readonly string[], into: Collector): void {
|
|
|
88
90
|
if (head === undefined) return;
|
|
89
91
|
const rest = tokens.slice(firstCommand(tokens) + 1);
|
|
90
92
|
const base = basename(head);
|
|
91
|
-
if (head.startsWith("$")) into.opaque
|
|
93
|
+
if (head.startsWith("$")) into.opaque.push(...tokens);
|
|
92
94
|
else if (base === "cd") visitCd(rest, into);
|
|
93
95
|
else if (SHELLS.has(base) || base === "eval") visitScript(base, rest, into);
|
|
94
|
-
else if (OPAQUE.has(base))
|
|
95
|
-
|
|
96
|
+
else if (OPAQUE.has(base)) {
|
|
97
|
+
if (gitIndex(rest) >= 0) into.opaque.push(...rest);
|
|
98
|
+
} else if (base === "git") visitGit(rest, into);
|
|
96
99
|
else visitWrapped(base, rest, into);
|
|
97
100
|
}
|
|
98
101
|
|
|
@@ -104,7 +107,7 @@ function visitWrapped(base: string, rest: readonly string[], into: Collector): v
|
|
|
104
107
|
|
|
105
108
|
function merge(into: Collector, other: GitResolution): void {
|
|
106
109
|
into.invocations.push(...other.invocations.map((g) => ({ ...g, line: into.line })));
|
|
107
|
-
|
|
110
|
+
into.opaque.push(...other.opaque);
|
|
108
111
|
}
|
|
109
112
|
|
|
110
113
|
/**
|
|
@@ -112,9 +115,10 @@ function merge(into: Collector, other: GitResolution): void {
|
|
|
112
115
|
* `timeout`, `if … then`), `bash -c`/`eval` scripts and `$(…)` substitutions. Heredoc bodies are data.
|
|
113
116
|
*/
|
|
114
117
|
export function resolveGit(command: string): GitResolution {
|
|
115
|
-
const into: Collector = { invocations: [], opaque:
|
|
118
|
+
const into: Collector = { invocations: [], opaque: [], dir: undefined, line: 0 };
|
|
116
119
|
let heredocEnd: string | undefined;
|
|
117
|
-
|
|
120
|
+
// Heredoc bodies are data (an apostrophe in one must not unbalance quote tracking).
|
|
121
|
+
const lines = splitLines(stripHeredocs(command).rest);
|
|
118
122
|
for (const [index, line] of lines.entries()) {
|
|
119
123
|
into.line = index;
|
|
120
124
|
if (heredocEnd !== undefined) {
|
|
@@ -127,3 +131,7 @@ export function resolveGit(command: string): GitResolution {
|
|
|
127
131
|
}
|
|
128
132
|
return into;
|
|
129
133
|
}
|
|
134
|
+
|
|
135
|
+
/** Whether something unreadable in the command may be running `git <subcommand>`. */
|
|
136
|
+
export const opaqueMentions = (resolution: GitResolution, subcommand: string): boolean =>
|
|
137
|
+
resolution.opaque.includes(subcommand);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const HEREDOC =
|
|
2
|
+
/<<-?[ \t]*(['"]?)([A-Za-z_][A-Za-z0-9_]*)\1([^\n]*)\n([\s\S]*?)\n[ \t]*\2[ \t]*(?:\n|$)/g;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Heredoc bodies are data: take them out so their quotes cannot confuse line splitting. The rest of
|
|
6
|
+
* the opening line stays (`| git commit -F -`) with a `#HD<n>` marker naming the removed body.
|
|
7
|
+
*/
|
|
8
|
+
export function stripHeredocs(command: string): { rest: string; bodies: string[] } {
|
|
9
|
+
const bodies: string[] = [];
|
|
10
|
+
const rest = command.replace(HEREDOC, (...m: unknown[]) => {
|
|
11
|
+
bodies.push(String(m[4]).trim());
|
|
12
|
+
return `${String(m[3])} #HD${bodies.length - 1}\n`;
|
|
13
|
+
});
|
|
14
|
+
return { rest, bodies };
|
|
15
|
+
}
|
package/src/core/push-command.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveGit } from "./git-invocations.ts";
|
|
1
|
+
import { opaqueMentions, resolveGit } from "./git-invocations.ts";
|
|
2
2
|
|
|
3
3
|
export type PushTarget = {
|
|
4
4
|
readonly remote: string | undefined;
|
|
@@ -73,7 +73,7 @@ export function pushTargets(command: string): PushTarget[] {
|
|
|
73
73
|
g.sub === "push" && !isDryRun(g.args) ? [toTarget(g.args, g.dir)] : [],
|
|
74
74
|
);
|
|
75
75
|
// git run through something opaque (xargs, `$CMD`): a push cannot be ruled out, so assume the worst.
|
|
76
|
-
const opaquePush = resolution
|
|
76
|
+
const opaquePush = opaqueMentions(resolution, "push");
|
|
77
77
|
return opaquePush
|
|
78
78
|
? [
|
|
79
79
|
...targets,
|
|
@@ -63,7 +63,8 @@ function messageNeeds(message: string): Need[] {
|
|
|
63
63
|
|
|
64
64
|
/** `-a`/`--all`, or a `git add` in the same command, widen the commit beyond what is staged. */
|
|
65
65
|
const stagesEverything = (command: string): boolean =>
|
|
66
|
-
/\bgit\
|
|
66
|
+
/\bgit\s+add\b/.test(command) ||
|
|
67
|
+
/\bgit\b[^;&|\n]*\bcommit\b[^;&|\n]*\s(?:-[a-zA-Z]*a[a-zA-Z]*|--all|--include)(?=\s|$|[;&|])/.test(
|
|
67
68
|
command,
|
|
68
69
|
);
|
|
69
70
|
|