@oh-my-pi/pi-git-tool 3.20.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/CHANGELOG.md +17 -0
- package/package.json +42 -0
- package/src/cache/git-cache.ts +35 -0
- package/src/errors.ts +84 -0
- package/src/git-tool.ts +169 -0
- package/src/index.ts +3 -0
- package/src/operations/add.ts +42 -0
- package/src/operations/blame.ts +23 -0
- package/src/operations/branch.ts +115 -0
- package/src/operations/checkout.ts +37 -0
- package/src/operations/cherry-pick.ts +39 -0
- package/src/operations/commit.ts +52 -0
- package/src/operations/diff.ts +206 -0
- package/src/operations/fetch.ts +49 -0
- package/src/operations/github/ci.ts +155 -0
- package/src/operations/github/issue.ts +132 -0
- package/src/operations/github/pr.ts +247 -0
- package/src/operations/github/release.ts +109 -0
- package/src/operations/log.ts +63 -0
- package/src/operations/merge.ts +62 -0
- package/src/operations/pull.ts +47 -0
- package/src/operations/push.ts +59 -0
- package/src/operations/rebase.ts +36 -0
- package/src/operations/restore.ts +19 -0
- package/src/operations/show.ts +102 -0
- package/src/operations/stash.ts +86 -0
- package/src/operations/status.ts +54 -0
- package/src/operations/tag.ts +79 -0
- package/src/parsers/blame-parser.ts +91 -0
- package/src/parsers/diff-parser.ts +162 -0
- package/src/parsers/log-parser.ts +43 -0
- package/src/parsers/status-parser.ts +93 -0
- package/src/render.ts +181 -0
- package/src/safety/guards.ts +144 -0
- package/src/safety/policies.ts +25 -0
- package/src/types.ts +668 -0
- package/src/utils.ts +128 -0
package/src/utils.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { Subprocess } from "bun";
|
|
2
|
+
import { detectGhError, detectGitError, GitError, GitErrorCode } from "./errors";
|
|
3
|
+
|
|
4
|
+
export interface ExecResult {
|
|
5
|
+
stdout: string;
|
|
6
|
+
stderr: string;
|
|
7
|
+
exitCode: number;
|
|
8
|
+
error?: GitError | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ExecOptions {
|
|
12
|
+
cwd?: string;
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
timeout?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function readStream(stream: ReadableStream<Uint8Array> | undefined): Promise<string> {
|
|
18
|
+
if (!stream) return "";
|
|
19
|
+
const reader = stream.getReader();
|
|
20
|
+
const chunks: Uint8Array[] = [];
|
|
21
|
+
try {
|
|
22
|
+
while (true) {
|
|
23
|
+
const { done, value } = await reader.read();
|
|
24
|
+
if (done) break;
|
|
25
|
+
chunks.push(value);
|
|
26
|
+
}
|
|
27
|
+
} finally {
|
|
28
|
+
reader.releaseLock();
|
|
29
|
+
}
|
|
30
|
+
return Buffer.concat(chunks).toString();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function exec(command: string, args: string[], options?: ExecOptions): Promise<ExecResult> {
|
|
34
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
35
|
+
const proc: Subprocess = Bun.spawn([command, ...args], {
|
|
36
|
+
cwd,
|
|
37
|
+
stdin: "ignore",
|
|
38
|
+
stdout: "pipe",
|
|
39
|
+
stderr: "pipe",
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
let killed = false;
|
|
43
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
44
|
+
|
|
45
|
+
const killProcess = () => {
|
|
46
|
+
if (!killed) {
|
|
47
|
+
killed = true;
|
|
48
|
+
proc.kill();
|
|
49
|
+
setTimeout(() => {
|
|
50
|
+
try {
|
|
51
|
+
proc.kill(9);
|
|
52
|
+
} catch {
|
|
53
|
+
// Ignore if already dead.
|
|
54
|
+
}
|
|
55
|
+
}, 5000);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
if (options?.signal) {
|
|
60
|
+
if (options.signal.aborted) {
|
|
61
|
+
killProcess();
|
|
62
|
+
} else {
|
|
63
|
+
options.signal.addEventListener("abort", killProcess, { once: true });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (options?.timeout && options.timeout > 0) {
|
|
68
|
+
timeoutId = setTimeout(() => {
|
|
69
|
+
killProcess();
|
|
70
|
+
}, options.timeout);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
74
|
+
readStream(proc.stdout as ReadableStream<Uint8Array>),
|
|
75
|
+
readStream(proc.stderr as ReadableStream<Uint8Array>),
|
|
76
|
+
proc.exited,
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
80
|
+
if (options?.signal) {
|
|
81
|
+
options.signal.removeEventListener("abort", killProcess);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return { stdout, stderr, exitCode: exitCode ?? 0 };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function git(args: string[], options?: ExecOptions): Promise<ExecResult> {
|
|
88
|
+
const gitPath = Bun.which("git");
|
|
89
|
+
if (!gitPath) {
|
|
90
|
+
return {
|
|
91
|
+
stdout: "",
|
|
92
|
+
stderr: "git not found",
|
|
93
|
+
exitCode: 127,
|
|
94
|
+
error: new GitError("git is not installed", GitErrorCode.UNKNOWN),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const result = await exec(gitPath, args, options);
|
|
98
|
+
return { ...result, error: detectGitError(result.stderr, result.exitCode) };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function gh(args: string[], options?: ExecOptions): Promise<ExecResult> {
|
|
102
|
+
const ghPath = Bun.which("gh");
|
|
103
|
+
if (!ghPath) {
|
|
104
|
+
return {
|
|
105
|
+
stdout: "",
|
|
106
|
+
stderr: "gh not found",
|
|
107
|
+
exitCode: 127,
|
|
108
|
+
error: new GitError("GitHub CLI is not installed", GitErrorCode.GH_NOT_INSTALLED),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const result = await exec(ghPath, args, options);
|
|
112
|
+
return { ...result, error: detectGhError(result.stderr, result.exitCode) };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function parseShortstat(text: string): { files: number; additions: number; deletions: number } | null {
|
|
116
|
+
const match = text.match(/(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?/);
|
|
117
|
+
if (!match) return null;
|
|
118
|
+
return {
|
|
119
|
+
files: Number.parseInt(match[1], 10),
|
|
120
|
+
additions: match[2] ? Number.parseInt(match[2], 10) : 0,
|
|
121
|
+
deletions: match[3] ? Number.parseInt(match[3], 10) : 0,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function isTruthy(value: string | undefined): boolean {
|
|
126
|
+
if (!value) return false;
|
|
127
|
+
return value === "1" || value.toLowerCase() === "true" || value.toLowerCase() === "yes";
|
|
128
|
+
}
|