pi-jar 0.1.8 → 0.2.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.
@@ -202,24 +202,44 @@ export async function createDelegateWorktree(cwd: string, signal?: AbortSignal):
202
202
  // and stage in bounded batches rather than two git processes per repository file.
203
203
  const entries: { rel: string; mode: string; oid: string }[] = [];
204
204
  const batched: { entry: (typeof entries)[number]; path: string; size: number }[] = [];
205
+ // Small bounded batches overlap independent file I/O without loading an entire repository
206
+ // into memory. All writes settle before failure cleanup removes the disposable worktree.
207
+ const pending: { rel: string; stat: Stats }[] = [];
208
+ let pendingBytes = 0;
209
+ const flush = async () => {
210
+ const chunk = pending.splice(0);
211
+ pendingBytes = 0;
212
+ const results = await Promise.allSettled(chunk.map(async ({ rel, stat }) => {
213
+ signal?.throwIfAborted();
214
+ const source = resolve(repoRoot, rel);
215
+ const bytes = stat.isSymbolicLink() ? Buffer.from(await readlink(source)) : await readFile(source);
216
+ signal?.throwIfAborted();
217
+ await writeSnapshot(root, rel, bytes, stat);
218
+ const entry = { rel, mode: stat.isSymbolicLink() ? "120000" : (stat.mode & 0o111) ? "100755" : "100644", oid: "" };
219
+ // stdin-paths follows symlinks and is line-based: hash those exceptional paths by bytes.
220
+ const path = resolve(root, rel);
221
+ if (stat.isSymbolicLink() || path.includes("\n")) entry.oid = (await gitText(root, ["hash-object", "-w", "--no-filters", "--stdin"], bytes, signal)).trim();
222
+ return { entry, path, size: bytes.length };
223
+ }));
224
+ const failure = results.find(result => result.status === "rejected");
225
+ if (failure?.status === "rejected") throw failure.reason;
226
+ for (const result of results) {
227
+ if (result.status !== "fulfilled") continue;
228
+ entries.push(result.value.entry);
229
+ if (!result.value.entry.oid) batched.push(result.value);
230
+ }
231
+ };
205
232
  for (const rel of paths) {
233
+ signal?.throwIfAborted();
206
234
  if (!safeFilePath(repoRoot, rel)) throw new Error("unsafe snapshot path: " + rel);
207
- const source = resolve(repoRoot, rel);
208
- const stat = statIfPresent(source);
235
+ const stat = statIfPresent(resolve(repoRoot, rel));
209
236
  if (!stat) continue;
210
237
  if (!stat.isFile() && !stat.isSymbolicLink()) throw new Error("unsupported snapshot file: " + rel);
211
- const bytes = stat.isSymbolicLink() ? Buffer.from(await readlink(source)) : await readFile(source);
212
- signal?.throwIfAborted();
213
- await writeSnapshot(root, rel, bytes, stat);
214
- const entry = { rel, mode: stat.isSymbolicLink() ? "120000" : (stat.mode & 0o111) ? "100755" : "100644", oid: "" };
215
- entries.push(entry);
216
- // The private copy holds exactly the bytes read above. Hashing it by path would follow a
217
- // symlink, and --stdin-paths is line-based, so those entries hash their bytes directly.
218
- const path = resolve(root, rel);
219
- if (stat.isSymbolicLink() || path.includes("\n")) {
220
- entry.oid = (await gitText(root, ["hash-object", "-w", "--no-filters", "--stdin"], bytes, signal)).trim();
221
- } else batched.push({ entry, path, size: bytes.length });
238
+ if (pending.length && (pending.length >= 4 || pendingBytes + stat.size > 32 * 1024 * 1024)) await flush();
239
+ pending.push({ rel, stat });
240
+ pendingBytes += stat.size;
222
241
  }
242
+ if (pending.length) await flush();
223
243
  const oids = await hashPaths(root, batched, true, signal);
224
244
  batched.forEach((item, index) => { item.entry.oid = oids[index]!; });
225
245
  if (entries.length) {