just-git 0.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/LICENSE +21 -0
- package/README.md +240 -0
- package/dist/index.d.ts +395 -0
- package/dist/index.js +646 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 blindmansion
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# just-git
|
|
2
|
+
|
|
3
|
+
Git implementation for virtual bash environments (particularly [just-bash](https://github.com/vercel-labs/just-bash)). Pure TypeScript, zero dependencies. Works in Node, Bun, Deno, and the browser. ~97 kB gzipped.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install just-git
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { Bash } from "just-bash";
|
|
15
|
+
import { createGit } from "just-git";
|
|
16
|
+
|
|
17
|
+
const git = createGit({
|
|
18
|
+
identity: { name: "Alice", email: "alice@example.com" },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const bash = new Bash({
|
|
22
|
+
cwd: "/repo",
|
|
23
|
+
customCommands: [git],
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
await bash.exec("git init");
|
|
27
|
+
await bash.exec("echo 'hello' > README.md");
|
|
28
|
+
await bash.exec("git add .");
|
|
29
|
+
await bash.exec('git commit -m "initial commit"');
|
|
30
|
+
await bash.exec("git log --oneline");
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Options
|
|
34
|
+
|
|
35
|
+
`createGit(options?)` accepts:
|
|
36
|
+
|
|
37
|
+
| Option | Description |
|
|
38
|
+
| ------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
39
|
+
| `identity` | Author/committer override. With `locked: true`, always wins over env vars and git config. Without `locked`, acts as a fallback. |
|
|
40
|
+
| `credentials` | `(url) => HttpAuth \| null` callback for Smart HTTP transport auth. |
|
|
41
|
+
| `disabled` | `GitCommandName[]` of subcommands to block (e.g. `["push", "rebase"]`). |
|
|
42
|
+
| `network` | `{ allowed?: string[] }` to restrict HTTP access by hostname or URL prefix. Set to `false` to block all network access. |
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
const git = createGit({
|
|
46
|
+
identity: { name: "Agent Bot", email: "bot@company.com", locked: true },
|
|
47
|
+
credentials: async (url) => ({ type: "bearer", token: "ghp_..." }),
|
|
48
|
+
disabled: ["rebase"],
|
|
49
|
+
network: { allowed: ["github.com"] },
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Middleware
|
|
54
|
+
|
|
55
|
+
Middleware wraps every `git <subcommand>` invocation. Each middleware receives a `CommandEvent` and a `next()` function. Call `next()` to proceed, or return an `ExecResult` to short-circuit. Middlewares compose in registration order (first registered = outermost).
|
|
56
|
+
|
|
57
|
+
The `CommandEvent` provides the execution context: `{ command, rawArgs, fs, cwd, env, stdin }`, plus optional `exec` and `signal` when available.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
// Audit log — record every command the agent runs
|
|
61
|
+
git.use(async (event, next) => {
|
|
62
|
+
const result = await next();
|
|
63
|
+
auditLog.push({ command: `git ${event.command}`, exitCode: result.exitCode });
|
|
64
|
+
return result;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Gate pushes on human approval
|
|
68
|
+
git.use(async (event, next) => {
|
|
69
|
+
if (event.command === "push" && !(await getHumanApproval(event.rawArgs))) {
|
|
70
|
+
return { stdout: "", stderr: "Push blocked — awaiting approval.\n", exitCode: 1 };
|
|
71
|
+
}
|
|
72
|
+
return next();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Block commits that add large files (uses event.fs to read the worktree)
|
|
76
|
+
git.use(async (event, next) => {
|
|
77
|
+
if (event.command === "add") {
|
|
78
|
+
for (const path of event.rawArgs.filter((a) => !a.startsWith("-"))) {
|
|
79
|
+
const resolved = path.startsWith("/") ? path : `${event.cwd}/${path}`;
|
|
80
|
+
const stat = await event.fs.stat(resolved).catch(() => null);
|
|
81
|
+
if (stat && stat.size > 5_000_000) {
|
|
82
|
+
return { stdout: "", stderr: `Blocked: ${path} exceeds 5 MB\n`, exitCode: 1 };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return next();
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`git.use()` returns an unsubscribe function to remove the middleware dynamically.
|
|
91
|
+
|
|
92
|
+
## Hooks
|
|
93
|
+
|
|
94
|
+
Hooks fire at specific points inside command execution (after middleware, inside operation logic). Register with `git.on(event, handler)`, which returns an unsubscribe function.
|
|
95
|
+
|
|
96
|
+
### Pre-hooks
|
|
97
|
+
|
|
98
|
+
Pre-hooks can abort the operation by returning `{ abort: true, message? }`.
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
// Block secrets from being committed
|
|
102
|
+
git.on("pre-commit", (event) => {
|
|
103
|
+
const forbidden = event.index.entries.filter((e) => /\.(env|pem|key)$/.test(e.path));
|
|
104
|
+
if (forbidden.length) {
|
|
105
|
+
return { abort: true, message: `Blocked: ${forbidden.map((e) => e.path).join(", ")}` };
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Enforce conventional commit messages
|
|
110
|
+
git.on("commit-msg", (event) => {
|
|
111
|
+
if (!/^(feat|fix|docs|refactor|test|chore)(\(.+\))?:/.test(event.message)) {
|
|
112
|
+
return { abort: true, message: "Commit message must follow conventional commits format" };
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
| Hook | Payload |
|
|
118
|
+
| ------------------ | --------------------------------------------------------------- |
|
|
119
|
+
| `pre-commit` | `{ index, treeHash }` |
|
|
120
|
+
| `commit-msg` | `{ message }` (mutable) |
|
|
121
|
+
| `merge-msg` | `{ message, treeHash, headHash, theirsHash }` (mutable message) |
|
|
122
|
+
| `pre-merge-commit` | `{ mergeMessage, treeHash, headHash, theirsHash }` |
|
|
123
|
+
| `pre-checkout` | `{ target, mode }` |
|
|
124
|
+
| `pre-push` | `{ remote, url, refs[] }` |
|
|
125
|
+
| `pre-fetch` | `{ remote, url, refspecs, prune, tags }` |
|
|
126
|
+
| `pre-clone` | `{ repository, targetPath, bare, branch }` |
|
|
127
|
+
| `pre-pull` | `{ remote, branch }` |
|
|
128
|
+
| `pre-rebase` | `{ upstream, branch }` |
|
|
129
|
+
| `pre-reset` | `{ mode, target }` |
|
|
130
|
+
| `pre-clean` | `{ dryRun, force, removeDirs, removeIgnored, onlyIgnored }` |
|
|
131
|
+
| `pre-rm` | `{ paths, cached, recursive, force }` |
|
|
132
|
+
| `pre-cherry-pick` | `{ mode, commit }` |
|
|
133
|
+
| `pre-revert` | `{ mode, commit }` |
|
|
134
|
+
| `pre-stash` | `{ action, ref }` |
|
|
135
|
+
|
|
136
|
+
### Post-hooks
|
|
137
|
+
|
|
138
|
+
Post-hooks are observational -- return value is ignored. Handlers are awaited in registration order.
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
// Feed agent activity to your UI or orchestration layer
|
|
142
|
+
git.on("post-commit", (event) => {
|
|
143
|
+
onAgentCommit({ hash: event.hash, branch: event.branch, message: event.message });
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
git.on("post-push", (event) => {
|
|
147
|
+
onAgentPush({ remote: event.remote, refs: event.refs });
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
| Hook | Payload |
|
|
152
|
+
| ------------------ | ------------------------------------------------ |
|
|
153
|
+
| `post-commit` | `{ hash, message, branch, parents, author }` |
|
|
154
|
+
| `post-merge` | `{ headHash, theirsHash, strategy, commitHash }` |
|
|
155
|
+
| `post-checkout` | `{ prevHead, newHead, isBranchCheckout }` |
|
|
156
|
+
| `post-push` | same payload as `pre-push` |
|
|
157
|
+
| `post-fetch` | `{ remote, url, refsUpdated }` |
|
|
158
|
+
| `post-clone` | `{ repository, targetPath, bare, branch }` |
|
|
159
|
+
| `post-pull` | `{ remote, branch, strategy, commitHash }` |
|
|
160
|
+
| `post-reset` | `{ mode, targetHash }` |
|
|
161
|
+
| `post-clean` | `{ removed, dryRun }` |
|
|
162
|
+
| `post-rm` | `{ removedPaths, cached }` |
|
|
163
|
+
| `post-cherry-pick` | `{ mode, commitHash, hadConflicts }` |
|
|
164
|
+
| `post-revert` | `{ mode, commitHash, hadConflicts }` |
|
|
165
|
+
| `post-stash` | `{ action, ok }` |
|
|
166
|
+
|
|
167
|
+
### Low-level events
|
|
168
|
+
|
|
169
|
+
Fire-and-forget events emitted on every object/ref write. Handler errors are caught and forwarded to `hooks.onError` (no-op by default).
|
|
170
|
+
|
|
171
|
+
| Event | Payload |
|
|
172
|
+
| -------------- | --------------------------- |
|
|
173
|
+
| `ref:update` | `{ ref, oldHash, newHash }` |
|
|
174
|
+
| `ref:delete` | `{ ref, oldHash }` |
|
|
175
|
+
| `object:write` | `{ type, hash }` |
|
|
176
|
+
|
|
177
|
+
## Command coverage
|
|
178
|
+
|
|
179
|
+
33 commands implemented. See [CLI.md](CLI.md) for full usage details.
|
|
180
|
+
|
|
181
|
+
| Command | Flags / options |
|
|
182
|
+
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
183
|
+
| `init [<dir>]` | `--bare`, `--initial-branch` |
|
|
184
|
+
| `clone <repo> [<dir>]` | `--bare`, `-b <branch>` |
|
|
185
|
+
| `blame <file>` | `-L <start>,<end>`, `-l`/`--long`, `-e`/`--show-email`, `-s`/`--suppress`, `-p`/`--porcelain`, `--line-porcelain` |
|
|
186
|
+
| `add <paths>` | `.`, `--all`/`-A`, `--update`/`-u`, `--force`/`-f`, `-n`/`--dry-run`, glob pathspecs |
|
|
187
|
+
| `rm <paths>` | `--cached`, `-r`, `-f`, `-n`/`--dry-run`, glob pathspecs |
|
|
188
|
+
| `mv <src> <dst>` | `-f`, `-n`/`--dry-run`, `-k` |
|
|
189
|
+
| `commit` | `-m`, `-F <file>` / `-F -`, `--allow-empty`, `--amend`, `--no-edit`, `-a` |
|
|
190
|
+
| `status` | `-s`/`--short`, `--porcelain`, `-b`/`--branch` |
|
|
191
|
+
| `log` | `--oneline`, `-n`, `--all`, `--reverse`, `--decorate`, `--format`/`--pretty`, `A..B`, `A...B`, `-- <path>`, `--author=`, `--grep=`, `--since`/`--after`, `--until`/`--before` |
|
|
192
|
+
| `show [<object>]` | Commits (with diff), annotated tags, trees, blobs |
|
|
193
|
+
| `diff` | `--cached`/`--staged`, `<commit>`, `<commit> <commit>`, `A..B`, `A...B`, `-- <path>`, `--stat`, `--shortstat`, `--numstat`, `--name-only`, `--name-status` |
|
|
194
|
+
| `branch` | `-d`, `-D`, `-m`, `-M`, `-r`, `-a`/`--all`, `-v`/`-vv`, `-u`/`--set-upstream-to` |
|
|
195
|
+
| `tag [<name>] [<commit>]` | `-a -m` (annotated), `-d`, `-l <pattern>`, `-f` |
|
|
196
|
+
| `switch` | `-c`/`-C` (create/force-create), `--detach`/`-d`, `--orphan`, `-` (previous branch), `--guess`/`--no-guess` |
|
|
197
|
+
| `restore` | `-s`/`--source`, `-S`/`--staged`, `-W`/`--worktree`, `-S -W` (both), `--ours`/`--theirs`, pathspec globs |
|
|
198
|
+
| `checkout` | `-b`, `-B`, `--orphan`, detached HEAD, `-- <paths>`, `--ours`/`--theirs`, pathspec globs |
|
|
199
|
+
| `reset [<commit>]` | `-- <paths>`, `--soft`, `--mixed`, `--hard`, pathspec globs |
|
|
200
|
+
| `merge <branch>` | `--no-ff`, `--ff-only`, `--squash`, `-m`, `--abort`, `--continue`, conflict markers |
|
|
201
|
+
| `revert <commit>` | `--abort`, `--continue`, `-n`/`--no-commit`, `--no-edit`, `-m`/`--mainline` |
|
|
202
|
+
| `cherry-pick <commit>` | `--abort`, `--continue`, `--skip`, `-x`, `-m`/`--mainline`, `-n`/`--no-commit`, preserves original author |
|
|
203
|
+
| `rebase <upstream>` | `--onto <newbase>`, `--abort`, `--continue`, `--skip` |
|
|
204
|
+
| `stash` | `push`, `pop`, `apply`, `list`, `drop`, `show`, `clear`, `-m`, `-u`/`--include-untracked`, `stash@{N}` |
|
|
205
|
+
| `remote` | `add`, `remove`/`rm`, `rename`, `set-url`, `get-url`, `-v` |
|
|
206
|
+
| `config` | `get`, `set`, `unset`, `list`, `--list`/`-l`, `--unset` |
|
|
207
|
+
| `fetch [<remote>] [<refspec>...]` | `--all`, `--tags`, `--prune`/`-p` |
|
|
208
|
+
| `push [<remote>] [<refspec>...]` | `--force`/`-f`, `-u`/`--set-upstream`, `--all`, `--tags`, `--delete`/`-d` |
|
|
209
|
+
| `pull [<remote>] [<branch>]` | `--ff-only`, `--no-ff`, `--rebase`/`-r`, `--no-rebase` |
|
|
210
|
+
| `clean` | `-f`, `-n`/`--dry-run`, `-d`, `-x`, `-X`, `-e`/`--exclude` |
|
|
211
|
+
| `reflog` | `show [<ref>]`, `exists`, `-n`/`--max-count` |
|
|
212
|
+
| `gc` | `--aggressive` |
|
|
213
|
+
| `repack` | `-a`/`--all`, `-d`/`--delete` |
|
|
214
|
+
| `rev-parse` | `--verify`, `--short`, `--abbrev-ref`, `--symbolic-full-name`, `--show-toplevel`, `--git-dir`, `--is-inside-work-tree`, `--is-bare-repository`, `--show-prefix`, `--show-cdup` |
|
|
215
|
+
| `ls-files` | `-c`/`--cached`, `-m`/`--modified`, `-d`/`--deleted`, `-o`/`--others`, `-u`/`--unmerged`, `-s`/`--stage`, `--exclude-standard`, `-z`, `-t` |
|
|
216
|
+
|
|
217
|
+
### Transport
|
|
218
|
+
|
|
219
|
+
- **Local paths** -- direct filesystem transfer between repositories.
|
|
220
|
+
- **Smart HTTP** -- clone, fetch, and push against real Git servers (e.g. GitHub) via Git Smart HTTP protocol. Auth via `credentials` option or `GIT_HTTP_BEARER_TOKEN` / `GIT_HTTP_USER` + `GIT_HTTP_PASSWORD` env vars.
|
|
221
|
+
|
|
222
|
+
### Internals
|
|
223
|
+
|
|
224
|
+
- `.gitignore` support (hierarchical, negation, `info/exclude`, `core.excludesFile`)
|
|
225
|
+
- Merge-ort strategy with rename detection and recursive merge bases
|
|
226
|
+
- Reflog for HEAD, branches, and tracking refs
|
|
227
|
+
- Index in Git binary v2 format
|
|
228
|
+
- Object storage in real Git format (SHA-1 addressed)
|
|
229
|
+
- Packfiles with zlib compression for storage and transport
|
|
230
|
+
- Pathspec globs across `add`, `rm`, `diff`, `reset`, `checkout`, `restore`, `log`
|
|
231
|
+
|
|
232
|
+
## Goals and testing
|
|
233
|
+
|
|
234
|
+
High fidelity to real git (2.53.0) state and output. Tested using real git as an [oracle](test/oracle/README.md) across hundreds of randomized command traces.
|
|
235
|
+
|
|
236
|
+
If you're running just-bash over a real filesystem, mixing commands between this implementation and real git in the same repo has not been extensively tested yet.
|
|
237
|
+
|
|
238
|
+
## Disclaimer
|
|
239
|
+
|
|
240
|
+
This project is not affiliated with [just-bash](https://github.com/vercel-labs/just-bash) or Vercel.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
interface FileStat {
|
|
2
|
+
isFile: boolean;
|
|
3
|
+
isDirectory: boolean;
|
|
4
|
+
isSymbolicLink: boolean;
|
|
5
|
+
mode: number;
|
|
6
|
+
size: number;
|
|
7
|
+
mtime: Date;
|
|
8
|
+
}
|
|
9
|
+
interface FileSystem {
|
|
10
|
+
readFile(path: string): Promise<string>;
|
|
11
|
+
readFileBuffer(path: string): Promise<Uint8Array>;
|
|
12
|
+
writeFile(path: string, content: string | Uint8Array): Promise<void>;
|
|
13
|
+
exists(path: string): Promise<boolean>;
|
|
14
|
+
stat(path: string): Promise<FileStat>;
|
|
15
|
+
mkdir(path: string, options?: {
|
|
16
|
+
recursive?: boolean;
|
|
17
|
+
}): Promise<void>;
|
|
18
|
+
readdir(path: string): Promise<string[]>;
|
|
19
|
+
rm(path: string, options?: {
|
|
20
|
+
recursive?: boolean;
|
|
21
|
+
force?: boolean;
|
|
22
|
+
}): Promise<void>;
|
|
23
|
+
/** Stat without following symlinks. Falls back to stat() semantics when not implemented. */
|
|
24
|
+
lstat?(path: string): Promise<FileStat>;
|
|
25
|
+
/** Read the target of a symbolic link. */
|
|
26
|
+
readlink?(path: string): Promise<string>;
|
|
27
|
+
/** Create a symbolic link pointing to target at the given path. */
|
|
28
|
+
symlink?(target: string, path: string): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** 40-character lowercase hex SHA-1 hash. */
|
|
32
|
+
type ObjectId = string;
|
|
33
|
+
/** The four Git object types. */
|
|
34
|
+
type ObjectType = "blob" | "tree" | "commit" | "tag";
|
|
35
|
+
/** Author or committer identity with timestamp. */
|
|
36
|
+
interface Identity {
|
|
37
|
+
name: string;
|
|
38
|
+
email: string;
|
|
39
|
+
/** Unix epoch seconds. */
|
|
40
|
+
timestamp: number;
|
|
41
|
+
/** Timezone offset string, e.g. "+0000", "-0500". */
|
|
42
|
+
timezone: string;
|
|
43
|
+
}
|
|
44
|
+
/** Stat-like metadata stored per index entry. */
|
|
45
|
+
interface IndexStat {
|
|
46
|
+
ctimeSeconds: number;
|
|
47
|
+
ctimeNanoseconds: number;
|
|
48
|
+
mtimeSeconds: number;
|
|
49
|
+
mtimeNanoseconds: number;
|
|
50
|
+
dev: number;
|
|
51
|
+
ino: number;
|
|
52
|
+
uid: number;
|
|
53
|
+
gid: number;
|
|
54
|
+
size: number;
|
|
55
|
+
}
|
|
56
|
+
interface IndexEntry {
|
|
57
|
+
/** File path relative to the work tree root. */
|
|
58
|
+
path: string;
|
|
59
|
+
/** File mode as a numeric value (e.g. 0o100644). */
|
|
60
|
+
mode: number;
|
|
61
|
+
/** SHA-1 of the blob content. */
|
|
62
|
+
hash: ObjectId;
|
|
63
|
+
/** Merge stage: 0 = normal, 1 = base, 2 = ours, 3 = theirs. */
|
|
64
|
+
stage: number;
|
|
65
|
+
stat: IndexStat;
|
|
66
|
+
}
|
|
67
|
+
interface Index {
|
|
68
|
+
version: number;
|
|
69
|
+
entries: IndexEntry[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type HttpAuth = {
|
|
73
|
+
type: "basic";
|
|
74
|
+
username: string;
|
|
75
|
+
password: string;
|
|
76
|
+
} | {
|
|
77
|
+
type: "bearer";
|
|
78
|
+
token: string;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
interface ExecResult {
|
|
82
|
+
stdout: string;
|
|
83
|
+
stderr: string;
|
|
84
|
+
exitCode: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
type CredentialProvider = (url: string) => HttpAuth | null | Promise<HttpAuth | null>;
|
|
88
|
+
interface IdentityOverride {
|
|
89
|
+
name: string;
|
|
90
|
+
email: string;
|
|
91
|
+
locked?: boolean;
|
|
92
|
+
}
|
|
93
|
+
type FetchFunction = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
94
|
+
interface NetworkPolicy {
|
|
95
|
+
/**
|
|
96
|
+
* Allowed URL patterns. Can be:
|
|
97
|
+
* - A hostname: "github.com" (matches any URL whose host equals this)
|
|
98
|
+
* - A URL prefix: "https://github.com/myorg/" (matches URLs starting with this)
|
|
99
|
+
*/
|
|
100
|
+
allowed?: string[];
|
|
101
|
+
/** Custom fetch function for HTTP transport. Falls back to globalThis.fetch. */
|
|
102
|
+
fetch?: FetchFunction;
|
|
103
|
+
}
|
|
104
|
+
interface PreCommitEvent {
|
|
105
|
+
readonly index: Index;
|
|
106
|
+
readonly treeHash: ObjectId;
|
|
107
|
+
}
|
|
108
|
+
interface CommitMsgEvent {
|
|
109
|
+
message: string;
|
|
110
|
+
}
|
|
111
|
+
interface MergeMsgEvent {
|
|
112
|
+
message: string;
|
|
113
|
+
readonly treeHash: ObjectId;
|
|
114
|
+
readonly headHash: ObjectId;
|
|
115
|
+
readonly theirsHash: ObjectId;
|
|
116
|
+
}
|
|
117
|
+
interface PostCommitEvent {
|
|
118
|
+
readonly hash: ObjectId;
|
|
119
|
+
readonly message: string;
|
|
120
|
+
readonly branch: string | null;
|
|
121
|
+
readonly parents: readonly ObjectId[];
|
|
122
|
+
readonly author: Identity;
|
|
123
|
+
}
|
|
124
|
+
interface PreMergeCommitEvent {
|
|
125
|
+
readonly mergeMessage: string;
|
|
126
|
+
readonly treeHash: ObjectId;
|
|
127
|
+
readonly headHash: ObjectId;
|
|
128
|
+
readonly theirsHash: ObjectId;
|
|
129
|
+
}
|
|
130
|
+
interface PostMergeEvent {
|
|
131
|
+
readonly headHash: ObjectId;
|
|
132
|
+
readonly theirsHash: ObjectId;
|
|
133
|
+
readonly strategy: "fast-forward" | "three-way";
|
|
134
|
+
readonly commitHash: ObjectId | null;
|
|
135
|
+
}
|
|
136
|
+
interface PostCheckoutEvent {
|
|
137
|
+
readonly prevHead: ObjectId | null;
|
|
138
|
+
readonly newHead: ObjectId;
|
|
139
|
+
readonly isBranchCheckout: boolean;
|
|
140
|
+
}
|
|
141
|
+
interface PrePushEvent {
|
|
142
|
+
readonly remote: string;
|
|
143
|
+
readonly url: string;
|
|
144
|
+
readonly refs: ReadonlyArray<{
|
|
145
|
+
srcRef: string | null;
|
|
146
|
+
srcHash: ObjectId | null;
|
|
147
|
+
dstRef: string;
|
|
148
|
+
dstHash: ObjectId | null;
|
|
149
|
+
force: boolean;
|
|
150
|
+
delete: boolean;
|
|
151
|
+
}>;
|
|
152
|
+
}
|
|
153
|
+
type PostPushEvent = PrePushEvent;
|
|
154
|
+
interface PreRebaseEvent {
|
|
155
|
+
readonly upstream: string;
|
|
156
|
+
readonly branch: string | null;
|
|
157
|
+
}
|
|
158
|
+
interface PreCheckoutEvent {
|
|
159
|
+
readonly target: string;
|
|
160
|
+
readonly mode: "switch" | "detach" | "create-branch" | "paths";
|
|
161
|
+
}
|
|
162
|
+
interface PreFetchEvent {
|
|
163
|
+
readonly remote: string;
|
|
164
|
+
readonly url: string;
|
|
165
|
+
readonly refspecs: readonly string[];
|
|
166
|
+
readonly prune: boolean;
|
|
167
|
+
readonly tags: boolean;
|
|
168
|
+
}
|
|
169
|
+
interface PostFetchEvent {
|
|
170
|
+
readonly remote: string;
|
|
171
|
+
readonly url: string;
|
|
172
|
+
readonly refsUpdated: number;
|
|
173
|
+
}
|
|
174
|
+
interface PreCloneEvent {
|
|
175
|
+
readonly repository: string;
|
|
176
|
+
readonly targetPath: string;
|
|
177
|
+
readonly bare: boolean;
|
|
178
|
+
readonly branch: string | null;
|
|
179
|
+
}
|
|
180
|
+
interface PostCloneEvent {
|
|
181
|
+
readonly repository: string;
|
|
182
|
+
readonly targetPath: string;
|
|
183
|
+
readonly bare: boolean;
|
|
184
|
+
readonly branch: string | null;
|
|
185
|
+
}
|
|
186
|
+
interface PrePullEvent {
|
|
187
|
+
readonly remote: string;
|
|
188
|
+
readonly branch: string | null;
|
|
189
|
+
}
|
|
190
|
+
interface PostPullEvent {
|
|
191
|
+
readonly remote: string;
|
|
192
|
+
readonly branch: string | null;
|
|
193
|
+
readonly strategy: "up-to-date" | "fast-forward" | "three-way";
|
|
194
|
+
readonly commitHash: ObjectId | null;
|
|
195
|
+
}
|
|
196
|
+
interface PreResetEvent {
|
|
197
|
+
readonly mode: "soft" | "mixed" | "hard" | "paths";
|
|
198
|
+
readonly target: string | null;
|
|
199
|
+
}
|
|
200
|
+
interface PostResetEvent {
|
|
201
|
+
readonly mode: "soft" | "mixed" | "hard" | "paths";
|
|
202
|
+
readonly targetHash: ObjectId | null;
|
|
203
|
+
}
|
|
204
|
+
interface PreCleanEvent {
|
|
205
|
+
readonly dryRun: boolean;
|
|
206
|
+
readonly force: boolean;
|
|
207
|
+
readonly removeDirs: boolean;
|
|
208
|
+
readonly removeIgnored: boolean;
|
|
209
|
+
readonly onlyIgnored: boolean;
|
|
210
|
+
}
|
|
211
|
+
interface PostCleanEvent {
|
|
212
|
+
readonly removed: readonly string[];
|
|
213
|
+
readonly dryRun: boolean;
|
|
214
|
+
}
|
|
215
|
+
interface PreRmEvent {
|
|
216
|
+
readonly paths: readonly string[];
|
|
217
|
+
readonly cached: boolean;
|
|
218
|
+
readonly recursive: boolean;
|
|
219
|
+
readonly force: boolean;
|
|
220
|
+
}
|
|
221
|
+
interface PostRmEvent {
|
|
222
|
+
readonly removedPaths: readonly string[];
|
|
223
|
+
readonly cached: boolean;
|
|
224
|
+
}
|
|
225
|
+
interface PreCherryPickEvent {
|
|
226
|
+
readonly mode: "pick" | "continue" | "abort";
|
|
227
|
+
readonly commit: string | null;
|
|
228
|
+
}
|
|
229
|
+
interface PostCherryPickEvent {
|
|
230
|
+
readonly mode: "pick" | "continue" | "abort";
|
|
231
|
+
readonly commitHash: ObjectId | null;
|
|
232
|
+
readonly hadConflicts: boolean;
|
|
233
|
+
}
|
|
234
|
+
interface PreRevertEvent {
|
|
235
|
+
readonly mode: "revert" | "continue" | "abort";
|
|
236
|
+
readonly commit: string | null;
|
|
237
|
+
}
|
|
238
|
+
interface PostRevertEvent {
|
|
239
|
+
readonly mode: "revert" | "continue" | "abort";
|
|
240
|
+
readonly commitHash: ObjectId | null;
|
|
241
|
+
readonly hadConflicts: boolean;
|
|
242
|
+
}
|
|
243
|
+
interface PreStashEvent {
|
|
244
|
+
readonly action: "push" | "pop" | "apply" | "list" | "drop" | "show" | "clear";
|
|
245
|
+
readonly ref: string | null;
|
|
246
|
+
}
|
|
247
|
+
interface PostStashEvent {
|
|
248
|
+
readonly action: "push" | "pop" | "apply" | "list" | "drop" | "show" | "clear";
|
|
249
|
+
readonly ok: boolean;
|
|
250
|
+
}
|
|
251
|
+
interface RefUpdateEvent {
|
|
252
|
+
readonly ref: string;
|
|
253
|
+
readonly oldHash: ObjectId | null;
|
|
254
|
+
readonly newHash: ObjectId;
|
|
255
|
+
}
|
|
256
|
+
interface RefDeleteEvent {
|
|
257
|
+
readonly ref: string;
|
|
258
|
+
readonly oldHash: ObjectId | null;
|
|
259
|
+
}
|
|
260
|
+
interface ObjectWriteEvent {
|
|
261
|
+
readonly type: ObjectType;
|
|
262
|
+
readonly hash: ObjectId;
|
|
263
|
+
}
|
|
264
|
+
interface HookEventMap {
|
|
265
|
+
"pre-commit": PreCommitEvent;
|
|
266
|
+
"commit-msg": CommitMsgEvent;
|
|
267
|
+
"merge-msg": MergeMsgEvent;
|
|
268
|
+
"post-commit": PostCommitEvent;
|
|
269
|
+
"pre-merge-commit": PreMergeCommitEvent;
|
|
270
|
+
"post-merge": PostMergeEvent;
|
|
271
|
+
"pre-checkout": PreCheckoutEvent;
|
|
272
|
+
"post-checkout": PostCheckoutEvent;
|
|
273
|
+
"pre-push": PrePushEvent;
|
|
274
|
+
"post-push": PostPushEvent;
|
|
275
|
+
"pre-fetch": PreFetchEvent;
|
|
276
|
+
"post-fetch": PostFetchEvent;
|
|
277
|
+
"pre-clone": PreCloneEvent;
|
|
278
|
+
"post-clone": PostCloneEvent;
|
|
279
|
+
"pre-pull": PrePullEvent;
|
|
280
|
+
"post-pull": PostPullEvent;
|
|
281
|
+
"pre-rebase": PreRebaseEvent;
|
|
282
|
+
"pre-reset": PreResetEvent;
|
|
283
|
+
"post-reset": PostResetEvent;
|
|
284
|
+
"pre-clean": PreCleanEvent;
|
|
285
|
+
"post-clean": PostCleanEvent;
|
|
286
|
+
"pre-rm": PreRmEvent;
|
|
287
|
+
"post-rm": PostRmEvent;
|
|
288
|
+
"pre-cherry-pick": PreCherryPickEvent;
|
|
289
|
+
"post-cherry-pick": PostCherryPickEvent;
|
|
290
|
+
"pre-revert": PreRevertEvent;
|
|
291
|
+
"post-revert": PostRevertEvent;
|
|
292
|
+
"pre-stash": PreStashEvent;
|
|
293
|
+
"post-stash": PostStashEvent;
|
|
294
|
+
"ref:update": RefUpdateEvent;
|
|
295
|
+
"ref:delete": RefDeleteEvent;
|
|
296
|
+
"object:write": ObjectWriteEvent;
|
|
297
|
+
}
|
|
298
|
+
type PreHookName = "pre-commit" | "commit-msg" | "merge-msg" | "pre-merge-commit" | "pre-checkout" | "pre-push" | "pre-fetch" | "pre-clone" | "pre-pull" | "pre-rebase" | "pre-reset" | "pre-clean" | "pre-rm" | "pre-cherry-pick" | "pre-revert" | "pre-stash";
|
|
299
|
+
interface AbortResult {
|
|
300
|
+
abort: true;
|
|
301
|
+
message?: string;
|
|
302
|
+
}
|
|
303
|
+
type PreHookHandler<E extends PreHookName> = (event: HookEventMap[E]) => void | AbortResult | Promise<void | AbortResult>;
|
|
304
|
+
type PostHookHandler<E extends keyof HookEventMap> = (event: HookEventMap[E]) => void | Promise<void>;
|
|
305
|
+
type HookHandler<E extends keyof HookEventMap> = E extends PreHookName ? PreHookHandler<E> : PostHookHandler<E>;
|
|
306
|
+
|
|
307
|
+
interface CommandEvent {
|
|
308
|
+
/** The git subcommand being invoked (e.g. "commit", "push"). */
|
|
309
|
+
command: string | undefined;
|
|
310
|
+
/** Arguments after the subcommand. */
|
|
311
|
+
rawArgs: string[];
|
|
312
|
+
/** Virtual filesystem — same instance custom commands receive from just-bash. */
|
|
313
|
+
fs: FileSystem;
|
|
314
|
+
/** Current working directory. */
|
|
315
|
+
cwd: string;
|
|
316
|
+
/** Environment variables. */
|
|
317
|
+
env: Map<string, string>;
|
|
318
|
+
/** Standard input content. */
|
|
319
|
+
stdin: string;
|
|
320
|
+
/** Execute a subcommand in the shell. Available when running via just-bash. */
|
|
321
|
+
exec?: (command: string, options: CommandExecOptions) => Promise<ExecResult>;
|
|
322
|
+
/** Abort signal for cooperative cancellation. */
|
|
323
|
+
signal?: AbortSignal;
|
|
324
|
+
}
|
|
325
|
+
type Middleware = (event: CommandEvent, next: () => Promise<ExecResult>) => ExecResult | Promise<ExecResult>;
|
|
326
|
+
declare class HookEmitter {
|
|
327
|
+
private listeners;
|
|
328
|
+
onError: (error: unknown) => void;
|
|
329
|
+
on<E extends keyof HookEventMap>(event: E, handler: HookHandler<E>): () => void;
|
|
330
|
+
/**
|
|
331
|
+
* Emit a pre-hook event. Returns an AbortResult if any handler aborts,
|
|
332
|
+
* or null if all handlers allow the operation to proceed.
|
|
333
|
+
*/
|
|
334
|
+
emitPre<E extends PreHookName>(event: E, data: HookEventMap[E]): Promise<AbortResult | null>;
|
|
335
|
+
/** Emit a post-hook event and await all handlers in order. */
|
|
336
|
+
emitPost<E extends keyof HookEventMap>(event: E, data: HookEventMap[E]): Promise<void>;
|
|
337
|
+
/** Emit low-level events (synchronous, fire-and-forget). */
|
|
338
|
+
emit<E extends keyof HookEventMap>(event: E, data: HookEventMap[E]): void;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Options for subcommand execution (mirrors just-bash's CommandExecOptions). */
|
|
342
|
+
interface CommandExecOptions {
|
|
343
|
+
env?: Record<string, string>;
|
|
344
|
+
replaceEnv?: boolean;
|
|
345
|
+
cwd: string;
|
|
346
|
+
stdin?: string;
|
|
347
|
+
signal?: AbortSignal;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Context provided to commands during execution.
|
|
351
|
+
* Shadows just-bash's CommandContext — structurally compatible
|
|
352
|
+
* so this library can run with or without just-bash.
|
|
353
|
+
*/
|
|
354
|
+
interface CommandContext {
|
|
355
|
+
fs: FileSystem;
|
|
356
|
+
cwd: string;
|
|
357
|
+
env: Map<string, string>;
|
|
358
|
+
stdin: string;
|
|
359
|
+
exec?: (command: string, options: CommandExecOptions) => Promise<ExecResult>;
|
|
360
|
+
signal?: AbortSignal;
|
|
361
|
+
}
|
|
362
|
+
type GitCommandName = "init" | "clone" | "fetch" | "pull" | "push" | "add" | "blame" | "commit" | "status" | "log" | "branch" | "tag" | "checkout" | "diff" | "reset" | "merge" | "cherry-pick" | "revert" | "rebase" | "mv" | "rm" | "remote" | "config" | "show" | "stash" | "rev-parse" | "ls-files" | "clean" | "switch" | "restore" | "reflog" | "repack" | "gc";
|
|
363
|
+
interface GitOptions {
|
|
364
|
+
credentials?: CredentialProvider;
|
|
365
|
+
identity?: IdentityOverride;
|
|
366
|
+
disabled?: GitCommandName[];
|
|
367
|
+
/** Network policy. Set to `false` to block all HTTP access. */
|
|
368
|
+
network?: NetworkPolicy | false;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Bundle of operator-level extensions threaded into command handlers
|
|
372
|
+
* via closures and merged onto GitContext after discovery.
|
|
373
|
+
*/
|
|
374
|
+
interface GitExtensions {
|
|
375
|
+
hooks?: HookEmitter;
|
|
376
|
+
credentialProvider?: CredentialProvider;
|
|
377
|
+
identityOverride?: IdentityOverride;
|
|
378
|
+
fetchFn?: FetchFunction;
|
|
379
|
+
networkPolicy?: NetworkPolicy | false;
|
|
380
|
+
}
|
|
381
|
+
declare class Git {
|
|
382
|
+
readonly name = "git";
|
|
383
|
+
readonly hooks: HookEmitter;
|
|
384
|
+
private middlewares;
|
|
385
|
+
private extensions;
|
|
386
|
+
private inner;
|
|
387
|
+
constructor(options?: GitOptions);
|
|
388
|
+
on<E extends keyof HookEventMap>(event: E, handler: HookHandler<E>): () => void;
|
|
389
|
+
use(middleware: Middleware): () => void;
|
|
390
|
+
execute: (args: string[], ctx: CommandContext) => Promise<ExecResult>;
|
|
391
|
+
private runMiddleware;
|
|
392
|
+
}
|
|
393
|
+
declare function createGit(options?: GitOptions): Git;
|
|
394
|
+
|
|
395
|
+
export { type AbortResult, type CommandContext, type CommandEvent, type CommandExecOptions, type CommitMsgEvent, type CredentialProvider, type ExecResult, type FetchFunction, type FileStat, type FileSystem, Git, type GitCommandName, type GitExtensions, type GitOptions, type HookEventMap, type HookHandler, type IdentityOverride, type MergeMsgEvent, type Middleware, type NetworkPolicy, type ObjectWriteEvent, type PostCheckoutEvent, type PostCherryPickEvent, type PostCleanEvent, type PostCloneEvent, type PostCommitEvent, type PostFetchEvent, type PostMergeEvent, type PostPullEvent, type PostPushEvent, type PostResetEvent, type PostRevertEvent, type PostRmEvent, type PostStashEvent, type PreCheckoutEvent, type PreCherryPickEvent, type PreCleanEvent, type PreCloneEvent, type PreCommitEvent, type PreFetchEvent, type PreMergeCommitEvent, type PrePullEvent, type PrePushEvent, type PreRebaseEvent, type PreResetEvent, type PreRevertEvent, type PreRmEvent, type PreStashEvent, type RefDeleteEvent, type RefUpdateEvent, createGit };
|