just-git 0.1.7 → 0.1.9

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  [![CI](https://github.com/blindmansion/just-git/actions/workflows/ci.yml/badge.svg)](https://github.com/blindmansion/just-git/actions/workflows/ci.yml)
4
4
  [![npm](https://img.shields.io/npm/v/just-git)](https://www.npmjs.com/package/just-git)
5
5
 
6
- 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. ~100 kB gzipped.
6
+ Git implementation for virtual bash environments (particularly [just-bash](https://github.com/vercel-labs/just-bash)). Pure TypeScript, zero dependencies. 34 commands implemented. Works in Node, Bun, Deno, and the browser. ~100 kB gzipped.
7
7
 
8
8
  ## Install
9
9
 
@@ -37,12 +37,13 @@ await bash.exec("git log --oneline");
37
37
 
38
38
  `createGit(options?)` accepts:
39
39
 
40
- | Option | Description |
41
- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
42
- | `identity` | Author/committer override. With `locked: true`, always wins over env vars and git config. Without `locked`, acts as a fallback. |
43
- | `credentials` | `(url) => HttpAuth \| null` callback for Smart HTTP transport auth. |
44
- | `disabled` | `GitCommandName[]` of subcommands to block (e.g. `["push", "rebase"]`). |
45
- | `network` | `{ allowed?: string[], fetch?: FetchFunction }` to restrict HTTP access and/or provide a custom `fetch` implementation. Set to `false` to block all network access. |
40
+ | Option | Description |
41
+ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
42
+ | `identity` | Author/committer override. With `locked: true`, always wins over env vars and git config. Without `locked`, acts as a fallback. |
43
+ | `credentials` | `(url) => HttpAuth \| null` callback for Smart HTTP transport auth. |
44
+ | `disabled` | `GitCommandName[]` of subcommands to block (e.g. `["push", "rebase"]`). |
45
+ | `network` | `{ allowed?: string[], fetch?: FetchFunction }` to restrict HTTP access and/or provide a custom `fetch` implementation. Set to `false` to block all network access. |
46
+ | `resolveRemote` | `(url) => GitContext \| null` callback for cross-VFS remote resolution. See [Multi-agent collaboration](#multi-agent-collaboration). |
46
47
 
47
48
  ```ts
48
49
  const git = createGit({
@@ -177,9 +178,55 @@ Fire-and-forget events emitted on every object/ref write. Handler errors are cau
177
178
  | `ref:delete` | `{ ref, oldHash }` |
178
179
  | `object:write` | `{ type, hash }` |
179
180
 
181
+ ## Multi-agent collaboration
182
+
183
+ Multiple agents can work on clones of the same repository in the same process, each with full VFS isolation. The `resolveRemote` option maps remote URLs to `GitContext` instances on other virtual filesystems, so clone/fetch/push/pull cross VFS boundaries without any network or shared filesystem.
184
+
185
+ ```ts
186
+ import { Bash, InMemoryFs } from "just-bash";
187
+ import { createGit, findGitDir } from "just-git";
188
+
189
+ // Origin repo on its own filesystem
190
+ const originFs = new InMemoryFs();
191
+ const setupBash = new Bash({
192
+ fs: originFs,
193
+ cwd: "/repo",
194
+ customCommands: [
195
+ createGit({ identity: { name: "Setup", email: "setup@example.com", locked: true } }),
196
+ ],
197
+ });
198
+ await setupBash.exec("git init");
199
+ await setupBash.exec("echo 'hello' > README.md");
200
+ await setupBash.exec("git add . && git commit -m 'initial'");
201
+
202
+ const originCtx = await findGitDir(originFs, "/repo");
203
+
204
+ // Each agent gets its own filesystem + resolveRemote pointing to origin
205
+ function createAgent(name: string, email: string) {
206
+ const agentFs = new InMemoryFs();
207
+ const git = createGit({
208
+ identity: { name, email, locked: true },
209
+ resolveRemote: (url) => (url === "/origin" ? originCtx : null),
210
+ });
211
+ return new Bash({ fs: agentFs, cwd: "/repo", customCommands: [git] });
212
+ }
213
+
214
+ const alice = createAgent("Alice", "alice@example.com");
215
+ const bob = createAgent("Bob", "bob@example.com");
216
+
217
+ await alice.exec("git clone /origin /repo", { cwd: "/" });
218
+ await bob.exec("git clone /origin /repo", { cwd: "/" });
219
+
220
+ // Alice and Bob work independently, push to origin, fetch each other's changes
221
+ ```
222
+
223
+ Concurrent pushes to the same remote are automatically serialized — if two agents push simultaneously, one succeeds and the other gets a proper non-fast-forward rejection, just like real git.
224
+
225
+ See [`examples/multi-agent.ts`](examples/multi-agent.ts) for a full working example with a coordinator agent that merges feature branches.
226
+
180
227
  ## Command coverage
181
228
 
182
- 34 commands implemented. See [CLI.md](CLI.md) for full usage details.
229
+ See [CLI.md](CLI.md) for full usage details.
183
230
 
184
231
  | Command | Flags / options |
185
232
  | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
package/dist/index.d.ts CHANGED
@@ -28,10 +28,37 @@ interface FileSystem {
28
28
  symlink?(target: string, path: string): Promise<void>;
29
29
  }
30
30
 
31
+ /**
32
+ * Git object storage: compressed loose objects for new writes, with
33
+ * retained packfiles from fetch/clone. Reads check loose first,
34
+ * then fall back to pack indices.
35
+ */
36
+ declare class PackedObjectStore {
37
+ private fs;
38
+ private gitDir;
39
+ private hooks?;
40
+ private packs;
41
+ private loadedPackNames;
42
+ private discoverPromise;
43
+ constructor(fs: FileSystem, gitDir: string, hooks?: HookEmitter | undefined);
44
+ write(type: ObjectType, content: Uint8Array): Promise<ObjectId>;
45
+ read(hash: ObjectId): Promise<RawObject>;
46
+ exists(hash: ObjectId): Promise<boolean>;
47
+ ingestPack(packData: Uint8Array): Promise<number>;
48
+ /** Scan `.git/objects/pack/` for existing pack/idx pairs. */
49
+ private discover;
50
+ private doDiscover;
51
+ }
52
+
31
53
  /** 40-character lowercase hex SHA-1 hash. */
32
54
  type ObjectId = string;
33
55
  /** The four Git object types. */
34
56
  type ObjectType = "blob" | "tree" | "commit" | "tag";
57
+ /** An object as stored in .git/objects — type + raw content bytes. */
58
+ interface RawObject {
59
+ type: ObjectType;
60
+ content: Uint8Array;
61
+ }
35
62
  /** Author or committer identity with timestamp. */
36
63
  interface Identity {
37
64
  name: string;
@@ -68,6 +95,32 @@ interface Index {
68
95
  version: number;
69
96
  entries: IndexEntry[];
70
97
  }
98
+ /**
99
+ * Bundles the filesystem handle with resolved repository paths.
100
+ * Threaded through all library functions so they don't need to
101
+ * re-discover the .git directory on every call.
102
+ */
103
+ interface GitContext {
104
+ fs: FileSystem;
105
+ /** Absolute path to the .git directory. */
106
+ gitDir: string;
107
+ /** Absolute path to the working tree root, or null for bare repos. */
108
+ workTree: string | null;
109
+ /** Hook emitter for operation hooks and low-level events. */
110
+ hooks?: HookEmitter;
111
+ /** Operator-provided credential resolver (bypasses env vars). */
112
+ credentialProvider?: CredentialProvider;
113
+ /** Operator-provided identity override for author/committer. */
114
+ identityOverride?: IdentityOverride;
115
+ /** Custom fetch function for HTTP transport. Falls back to globalThis.fetch. */
116
+ fetchFn?: FetchFunction;
117
+ /** Network access policy. `false` blocks all HTTP access. */
118
+ networkPolicy?: NetworkPolicy | false;
119
+ /** Resolves remote URLs to GitContexts on potentially different VFS instances. */
120
+ resolveRemote?: RemoteResolver;
121
+ /** Cached object store instance. Lazily created by object-db. */
122
+ objectStore?: PackedObjectStore;
123
+ }
71
124
 
72
125
  type HttpAuth = {
73
126
  type: "basic";
@@ -90,6 +143,12 @@ interface IdentityOverride {
90
143
  email: string;
91
144
  locked?: boolean;
92
145
  }
146
+ /**
147
+ * Resolves a remote URL to a GitContext, enabling cross-VFS transport.
148
+ * Called before local filesystem lookup for non-HTTP URLs.
149
+ * Return null to fall back to local filesystem resolution.
150
+ */
151
+ type RemoteResolver = (url: string) => GitContext | null | Promise<GitContext | null>;
93
152
  type FetchFunction = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
94
153
  interface NetworkPolicy {
95
154
  /**
@@ -366,6 +425,12 @@ interface GitOptions {
366
425
  disabled?: GitCommandName[];
367
426
  /** Network policy. Set to `false` to block all HTTP access. */
368
427
  network?: NetworkPolicy | false;
428
+ /**
429
+ * Resolve a remote URL to a GitContext on a potentially different VFS.
430
+ * Called before local filesystem lookup for non-HTTP remote URLs.
431
+ * Return null to fall back to local filesystem resolution.
432
+ */
433
+ resolveRemote?: RemoteResolver;
369
434
  }
370
435
  /**
371
436
  * Bundle of operator-level extensions threaded into command handlers
@@ -377,6 +442,7 @@ interface GitExtensions {
377
442
  identityOverride?: IdentityOverride;
378
443
  fetchFn?: FetchFunction;
379
444
  networkPolicy?: NetworkPolicy | false;
445
+ resolveRemote?: RemoteResolver;
380
446
  }
381
447
  declare class Git {
382
448
  readonly name = "git";
@@ -392,4 +458,12 @@ declare class Git {
392
458
  }
393
459
  declare function createGit(options?: GitOptions): Git;
394
460
 
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 };
461
+ /**
462
+ * Walk up from `startPath` looking for a git repository.
463
+ * Checks for both normal repos (`.git/` subdirectory) and bare repos
464
+ * (`HEAD` + `objects/` + `refs/` directly in the directory).
465
+ * Returns a GitContext if found, null otherwise.
466
+ */
467
+ declare function findGitDir(fs: FileSystem, startPath: string): Promise<GitContext | null>;
468
+
469
+ 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 GitContext, 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, type RemoteResolver, createGit, findGitDir };