@nimbus-sh/worker 0.3.0 → 0.5.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/README.md +46 -43
- package/dist/_shared/preview-host.d.ts +14 -1
- package/dist/_shared/preview-host.d.ts.map +1 -1
- package/dist/_shared/preview-host.js +28 -3
- package/dist/_shared/session-router.d.ts +43 -0
- package/dist/_shared/session-router.d.ts.map +1 -1
- package/dist/_shared/session-router.js +59 -12
- package/dist/esbuild-wasm-bundle.generated.d.ts +1 -1
- package/dist/esbuild-wasm-bundle.generated.js +1 -1
- package/dist/facets/cirrus-real.js +1 -1
- package/dist/facets/durable-images.d.ts +50 -0
- package/dist/facets/durable-images.d.ts.map +1 -0
- package/dist/facets/durable-images.js +75 -0
- package/dist/facets/durable-slots.d.ts +45 -0
- package/dist/facets/durable-slots.d.ts.map +1 -0
- package/dist/facets/durable-slots.js +94 -0
- package/dist/facets/esbuild-transform.d.ts +10 -0
- package/dist/facets/esbuild-transform.d.ts.map +1 -0
- package/dist/facets/esbuild-transform.js +36 -0
- package/dist/facets/manager.d.ts +167 -12
- package/dist/facets/manager.d.ts.map +1 -1
- package/dist/facets/manager.js +575 -113
- package/dist/facets/opencode-staging.d.ts.map +1 -1
- package/dist/facets/opencode-staging.js +3 -4
- package/dist/facets/real-vite-hmr.js +1 -1
- package/dist/facets/vite-dev-server.d.ts +4 -4
- package/dist/facets/vite-dev-server.js +5 -5
- package/dist/git/commands.d.ts +22 -0
- package/dist/git/commands.d.ts.map +1 -1
- package/dist/git/commands.js +431 -424
- package/dist/git/network-facet.d.ts.map +1 -1
- package/dist/git/network-facet.js +6 -5
- package/dist/git-bundle.generated.d.ts +2 -2
- package/dist/git-bundle.generated.d.ts.map +1 -1
- package/dist/git-bundle.generated.js +3 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -2
- package/dist/loaders/child-process/spawn-facet.js +1 -1
- package/dist/loaders/child-process/spawn-pool.d.ts.map +1 -1
- package/dist/loaders/child-process/spawn-pool.js +2 -2
- package/dist/loaders/generated-workers.d.ts +2 -2
- package/dist/loaders/generated-workers.d.ts.map +1 -1
- package/dist/loaders/generated-workers.js +3 -3
- package/dist/loaders/npm-resolve-preamble.d.ts +3 -3
- package/dist/loaders/npm-resolve-preamble.js +3 -3
- package/dist/loaders/pre-bundle-preamble.d.ts +5 -5
- package/dist/loaders/pre-bundle-preamble.js +6 -6
- package/dist/node-shims-artifact.generated.js +3 -3
- package/dist/npm/install-batch-facet.d.ts +1 -1
- package/dist/npm/install-batch-facet.js +1 -1
- package/dist/npm/installer.d.ts +6 -6
- package/dist/npm/installer.d.ts.map +1 -1
- package/dist/npm/installer.js +28 -27
- package/dist/npm/pre-bundle-facet.d.ts +2 -2
- package/dist/npm/pre-bundle-facet.js +5 -5
- package/dist/npm/r2-cache.d.ts +1 -1
- package/dist/npm/resolve-facet.d.ts +1 -1
- package/dist/npm/resolve-facet.js +1 -1
- package/dist/npm/resolve-one-facet.d.ts +5 -5
- package/dist/npm/resolve-one-facet.js +5 -5
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +61 -4
- package/dist/router/public-directory-do.d.ts +17 -0
- package/dist/router/public-directory-do.d.ts.map +1 -0
- package/dist/router/public-directory-do.js +16 -0
- package/dist/router/public-directory.d.ts +75 -0
- package/dist/router/public-directory.d.ts.map +1 -0
- package/dist/router/public-directory.js +115 -0
- package/dist/router/remote-api.d.ts +9 -1
- package/dist/router/remote-api.d.ts.map +1 -1
- package/dist/router/remote-api.js +20 -3
- package/dist/runtime/bun-repl.d.ts +1 -1
- package/dist/runtime/bun-repl.js +3 -3
- package/dist/runtime/esbuild-wasm-bytes.js +1 -1
- package/dist/runtime/facet-loader-host.d.ts +2 -2
- package/dist/runtime/facet-loader-host.js +3 -3
- package/dist/runtime/node-repl.js +2 -2
- package/dist/runtime/node-shims-artifact.js +1 -1
- package/dist/runtime/node-shims.d.ts +4 -1
- package/dist/runtime/node-shims.d.ts.map +1 -1
- package/dist/runtime/node-shims.js +326 -7
- package/dist/runtime/opencode-artifact.js +1 -1
- package/dist/runtime/opentui-wasm-bytes.js +1 -1
- package/dist/runtime/python-repl.js +3 -3
- package/dist/runtime/ruby-repl.js +2 -2
- package/dist/runtime/sqlite-wasm-bytes.js +1 -1
- package/dist/session/agent.d.ts +11 -0
- package/dist/session/agent.d.ts.map +1 -1
- package/dist/session/diag.js +1 -1
- package/dist/session/helpers.js +1 -1
- package/dist/session/hibernation.d.ts +8 -8
- package/dist/session/hibernation.d.ts.map +1 -1
- package/dist/session/hibernation.js +15 -15
- package/dist/session/init-phases.d.ts +19 -26
- package/dist/session/init-phases.d.ts.map +1 -1
- package/dist/session/init-phases.js +20 -43
- package/dist/session/init.d.ts.map +1 -1
- package/dist/session/init.js +24 -19
- package/dist/session/keys.d.ts +8 -0
- package/dist/session/keys.d.ts.map +1 -1
- package/dist/session/keys.js +12 -3
- package/dist/session/nimbus-session.d.ts +33 -19
- package/dist/session/nimbus-session.d.ts.map +1 -1
- package/dist/session/nimbus-session.js +78 -37
- package/dist/session/port-capability.d.ts +95 -8
- package/dist/session/port-capability.d.ts.map +1 -1
- package/dist/session/port-capability.js +165 -8
- package/dist/session/programmatic.d.ts +50 -4
- package/dist/session/programmatic.d.ts.map +1 -1
- package/dist/session/programmatic.js +112 -15
- package/dist/session/routes.d.ts +17 -0
- package/dist/session/routes.d.ts.map +1 -1
- package/dist/session/routes.js +111 -43
- package/dist/session/rpc.d.ts +7 -7
- package/dist/session/rpc.d.ts.map +1 -1
- package/dist/session/rpc.js +34 -23
- package/dist/session/shell-socket.d.ts +76 -0
- package/dist/session/shell-socket.d.ts.map +1 -0
- package/dist/session/shell-socket.js +172 -0
- package/dist/session/start-real-vite.js +2 -2
- package/dist/session/supervisor-op.d.ts +5 -0
- package/dist/session/supervisor-op.d.ts.map +1 -0
- package/dist/session/supervisor-op.js +74 -0
- package/dist/session/supervisor-rpc.d.ts +6 -10
- package/dist/session/supervisor-rpc.d.ts.map +1 -1
- package/dist/session/supervisor-rpc.js +98 -79
- package/dist/session/ws.d.ts +1 -3
- package/dist/session/ws.d.ts.map +1 -1
- package/dist/session/ws.js +23 -7
- package/package.json +4 -3
- package/public/_assets/runtime/{node-shims-fea906843f6d594e.js → node-shims-9e1380d9ee430fcc.js} +320 -5
- package/public/s/index.html +101 -5
- package/scripts/bundle-esbuild-wasm.mjs +2 -2
- package/scripts/bundle-facet-workers.mjs +5 -7
- package/scripts/bundle-sqlite-wasm.mjs +1 -1
package/dist/git/commands.js
CHANGED
|
@@ -246,474 +246,481 @@ function getAuthor(ctx) {
|
|
|
246
246
|
}
|
|
247
247
|
// ── Git subcommand implementations ──────────────────────────────────────
|
|
248
248
|
export function registerGitCommands(registry, vfs, doCtx, doEnv) {
|
|
249
|
-
registry.register('git',
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
249
|
+
registry.register('git', (ctx) => runGitCommand(ctx, vfs, doCtx, doEnv));
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* The `git` command handler. Split out from registration so it can be
|
|
253
|
+
* lazy-loaded (`await import('./commands.js')`) on first `git` use, keeping
|
|
254
|
+
* this module and its ~106 KB network-facet dependency out of the cold
|
|
255
|
+
* script-eval graph.
|
|
256
|
+
*/
|
|
257
|
+
export async function runGitCommand(ctx, vfs, doCtx, doEnv) {
|
|
258
|
+
const credentialedVfs = vfs.as(requireVfsCred(ctx.cred, 'git'));
|
|
259
|
+
const fs = createGitFs(credentialedVfs);
|
|
260
|
+
const args = ctx.args;
|
|
261
|
+
const sub = args[0];
|
|
262
|
+
const subArgs = args.slice(1);
|
|
263
|
+
const dir = getDir(ctx);
|
|
264
|
+
if (sub === '--version' || sub === '-v') {
|
|
265
|
+
ctx.stdout.write('git version 2.44.0 (isomorphic-git/cf-git)\n');
|
|
266
|
+
return 0;
|
|
267
|
+
}
|
|
268
|
+
if (!sub || sub === '--help' || sub === '-h') {
|
|
269
|
+
ctx.stdout.write('usage: git <command> [<args>]\n\n');
|
|
270
|
+
ctx.stdout.write('Commands:\n');
|
|
271
|
+
ctx.stdout.write(' init, clone, status, add, commit, log, branch,\n');
|
|
272
|
+
ctx.stdout.write(' checkout, diff, remote, fetch, pull, push, merge,\n');
|
|
273
|
+
ctx.stdout.write(' reset, tag, config, --version\n');
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
// Lazy-load isomorphic-git only when actually needed.
|
|
277
|
+
// Note: http transport isn't loaded here — network ops (clone/fetch/pull)
|
|
278
|
+
// run inside the git-network-facet which imports its own http transport.
|
|
279
|
+
let git;
|
|
280
|
+
try {
|
|
281
|
+
git = await getGit();
|
|
282
|
+
}
|
|
283
|
+
catch (e) {
|
|
284
|
+
ctx.stderr.write(`git: failed to load git module: ${e?.message}\n`);
|
|
285
|
+
return 1;
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
switch (sub) {
|
|
289
|
+
case 'init': {
|
|
290
|
+
// git init [path] — if path given, use it; otherwise use cwd
|
|
291
|
+
let initDir = dir;
|
|
292
|
+
const initPath = subArgs.find((a) => !a.startsWith('-'));
|
|
293
|
+
if (initPath) {
|
|
294
|
+
initDir = initPath.startsWith('/') ? initPath : dir + '/' + initPath;
|
|
295
|
+
// Ensure the target directory exists in VFS
|
|
296
|
+
const stripped = initDir.replace(/^\/+/, '');
|
|
297
|
+
if (!credentialedVfs.exists(stripped))
|
|
298
|
+
credentialedVfs.mkdir(stripped, { recursive: true });
|
|
295
299
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
// <cwd>//tmp/x (note double slash) and the user's later `cd /tmp/x`
|
|
306
|
-
// hit ENOENT. Real-world git treats absolute targets as absolute.
|
|
307
|
-
let dest;
|
|
308
|
-
if (destArg) {
|
|
309
|
-
dest = destArg.startsWith('/')
|
|
310
|
-
? destArg
|
|
311
|
-
: getDir(ctx) + '/' + destArg;
|
|
312
|
-
}
|
|
313
|
-
else {
|
|
314
|
-
dest = dir + '/' + url.split('/').pop()?.replace('.git', '');
|
|
315
|
-
}
|
|
316
|
-
if (!doCtx || !doEnv) {
|
|
317
|
-
ctx.stderr.write('[git] clone requires DO ctx + env (internal configuration error)\n');
|
|
318
|
-
return 1;
|
|
319
|
-
}
|
|
320
|
-
ctx.stdout.write(`Cloning into '${dest}'...${depth ? ' (shallow, depth=' + depth + ')' : ''}\n`);
|
|
321
|
-
// A clone's closed-world filesystem view is correct only while no
|
|
322
|
-
// other session surface can mutate its destination subtree. Acquire
|
|
323
|
-
// the lease before the facet performs its lstat/readdir emptiness
|
|
324
|
-
// proof; the clone's W7 stream carries the opaque owner capability
|
|
325
|
-
// through the trusted SupervisorRPC binding.
|
|
326
|
-
const mutationLease = vfs.acquireExclusiveMutation(dest, {
|
|
327
|
-
includeMissingAncestors: true,
|
|
328
|
-
});
|
|
329
|
-
// Delegate to git-network-facet: heavy packfile processing runs in
|
|
330
|
-
// a dynamic worker with its own CPU budget, not the supervisor DO.
|
|
331
|
-
const doClone = async () => {
|
|
332
|
-
try {
|
|
333
|
-
const result = await execGitNetwork(doCtx, doEnv, {
|
|
334
|
-
op: 'clone',
|
|
335
|
-
pid: ctx.pid,
|
|
336
|
-
dir: dest,
|
|
337
|
-
url,
|
|
338
|
-
ref: branch,
|
|
339
|
-
depth,
|
|
340
|
-
exclusiveDestination: true,
|
|
341
|
-
exclusiveMutationRoot: mutationLease.root,
|
|
342
|
-
mutationOwner: mutationLease.owner,
|
|
343
|
-
// Verification/tuning knob: force a small per-chunk entry bound
|
|
344
|
-
// so ordinary repos exercise the multi-invocation chunked
|
|
345
|
-
// checkout path. Unset in production → the 10k default applies.
|
|
346
|
-
checkoutChunkMaxEntries: ctx.env.NIMBUS_GIT_CHECKOUT_CHUNK_ENTRIES
|
|
347
|
-
? Number(ctx.env.NIMBUS_GIT_CHECKOUT_CHUNK_ENTRIES) || undefined
|
|
348
|
-
: undefined,
|
|
349
|
-
auth: {
|
|
350
|
-
username: ctx.env.GIT_USERNAME || '',
|
|
351
|
-
password: ctx.env.GIT_PASSWORD || ctx.env.GIT_TOKEN || '',
|
|
352
|
-
},
|
|
353
|
-
});
|
|
354
|
-
if (result.success) {
|
|
355
|
-
ctx.stdout.write(`\n[git] clone complete (${result.filesWritten} files, ` +
|
|
356
|
-
`${(result.bytesWritten / 1024).toFixed(1)}KB in ${(result.elapsed / 1000).toFixed(1)}s)\n`);
|
|
357
|
-
}
|
|
358
|
-
else {
|
|
359
|
-
ctx.stderr.write(`\n[git] clone failed: ${result.error}\n`);
|
|
360
|
-
}
|
|
361
|
-
return result.success;
|
|
362
|
-
}
|
|
363
|
-
finally {
|
|
364
|
-
vfs.releaseExclusiveMutation(mutationLease.owner);
|
|
365
|
-
}
|
|
366
|
-
};
|
|
367
|
-
if (isBg) {
|
|
368
|
-
const task = doClone();
|
|
369
|
-
doCtx.waitUntil(task);
|
|
370
|
-
ctx.stdout.write('[git] clone running in background...\n');
|
|
371
|
-
return 0;
|
|
372
|
-
}
|
|
373
|
-
else {
|
|
374
|
-
return (await doClone()) ? 0 : 1;
|
|
375
|
-
}
|
|
300
|
+
await git.init({ fs, dir: initDir });
|
|
301
|
+
ctx.stdout.write(`Initialized empty Git repository in ${initDir}/.git/\n`);
|
|
302
|
+
return 0;
|
|
303
|
+
}
|
|
304
|
+
case 'clone': {
|
|
305
|
+
const { url, dest: destArg, depth, isBg, branch } = parseCloneArgs(subArgs);
|
|
306
|
+
if (!url) {
|
|
307
|
+
ctx.stderr.write(CLONE_USAGE + '\n');
|
|
308
|
+
return 1;
|
|
376
309
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
ctx.stdout.write(`\x1b[32mA ${filepath}\x1b[0m\n`);
|
|
388
|
-
else if (head === 1 && workdir === 2 && stage === 2)
|
|
389
|
-
ctx.stdout.write(`\x1b[32mM ${filepath}\x1b[0m\n`);
|
|
390
|
-
else if (head === 1 && workdir === 2 && stage === 1)
|
|
391
|
-
ctx.stdout.write(`\x1b[31m M ${filepath}\x1b[0m\n`);
|
|
392
|
-
else if (head === 1 && workdir === 0)
|
|
393
|
-
ctx.stdout.write(`\x1b[31m D ${filepath}\x1b[0m\n`);
|
|
394
|
-
else if (head === 1 && stage === 0)
|
|
395
|
-
ctx.stdout.write(`\x1b[32mD ${filepath}\x1b[0m\n`);
|
|
396
|
-
else
|
|
397
|
-
ctx.stdout.write(` ${filepath} [${head},${workdir},${stage}]\n`);
|
|
398
|
-
}
|
|
399
|
-
if (clean)
|
|
400
|
-
ctx.stdout.write('nothing to commit, working tree clean\n');
|
|
401
|
-
return 0;
|
|
310
|
+
// hardening-r5: respect absolute paths. Pre-fix `git clone <url> /tmp/x`
|
|
311
|
+
// resolved to `<cwd>//tmp/x` because the `subArgs[1]` branch
|
|
312
|
+
// unconditionally prepended getDir(ctx). The clone "succeeded" into
|
|
313
|
+
// <cwd>//tmp/x (note double slash) and the user's later `cd /tmp/x`
|
|
314
|
+
// hit ENOENT. Real-world git treats absolute targets as absolute.
|
|
315
|
+
let dest;
|
|
316
|
+
if (destArg) {
|
|
317
|
+
dest = destArg.startsWith('/')
|
|
318
|
+
? destArg
|
|
319
|
+
: getDir(ctx) + '/' + destArg;
|
|
402
320
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if (paths.length === 0 || paths.includes('.')) {
|
|
406
|
-
// Add all
|
|
407
|
-
const matrix = await git.statusMatrix({ fs, dir });
|
|
408
|
-
for (const [filepath, head, workdir, stage] of matrix) {
|
|
409
|
-
if (head !== workdir || workdir !== stage) {
|
|
410
|
-
if (workdir === 0)
|
|
411
|
-
await git.remove({ fs, dir, filepath });
|
|
412
|
-
else
|
|
413
|
-
await git.add({ fs, dir, filepath });
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
else {
|
|
418
|
-
for (const filepath of paths) {
|
|
419
|
-
await git.add({ fs, dir, filepath });
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
return 0;
|
|
321
|
+
else {
|
|
322
|
+
dest = dir + '/' + url.split('/').pop()?.replace('.git', '');
|
|
423
323
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
if (!message) {
|
|
428
|
-
ctx.stderr.write('error: empty commit message\n');
|
|
429
|
-
return 1;
|
|
430
|
-
}
|
|
431
|
-
const sha = await git.commit({
|
|
432
|
-
fs, dir, message,
|
|
433
|
-
author: getAuthor(ctx),
|
|
434
|
-
});
|
|
435
|
-
ctx.stdout.write(`[${sha.slice(0, 7)}] ${message}\n`);
|
|
436
|
-
return 0;
|
|
324
|
+
if (!doCtx || !doEnv) {
|
|
325
|
+
ctx.stderr.write('[git] clone requires DO ctx + env (internal configuration error)\n');
|
|
326
|
+
return 1;
|
|
437
327
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
328
|
+
ctx.stdout.write(`Cloning into '${dest}'...${depth ? ' (shallow, depth=' + depth + ')' : ''}\n`);
|
|
329
|
+
// A clone's closed-world filesystem view is correct only while no
|
|
330
|
+
// other session surface can mutate its destination subtree. Acquire
|
|
331
|
+
// the lease before the facet performs its lstat/readdir emptiness
|
|
332
|
+
// proof; the clone's W7 stream carries the opaque owner capability
|
|
333
|
+
// through the trusted SupervisorRPC binding.
|
|
334
|
+
const mutationLease = vfs.acquireExclusiveMutation(dest, {
|
|
335
|
+
includeMissingAncestors: true,
|
|
336
|
+
});
|
|
337
|
+
// Delegate to git-network-facet: heavy packfile processing runs in
|
|
338
|
+
// a dynamic worker with its own CPU budget, not the supervisor DO.
|
|
339
|
+
const doClone = async () => {
|
|
340
|
+
try {
|
|
341
|
+
const result = await execGitNetwork(doCtx, doEnv, {
|
|
342
|
+
op: 'clone',
|
|
343
|
+
pid: ctx.pid,
|
|
344
|
+
dir: dest,
|
|
345
|
+
url,
|
|
346
|
+
ref: branch,
|
|
347
|
+
depth,
|
|
348
|
+
exclusiveDestination: true,
|
|
349
|
+
exclusiveMutationRoot: mutationLease.root,
|
|
350
|
+
mutationOwner: mutationLease.owner,
|
|
351
|
+
// Verification/tuning knob: force a small per-chunk entry bound
|
|
352
|
+
// so ordinary repos exercise the multi-invocation chunked
|
|
353
|
+
// checkout path. Unset in production → the 10k default applies.
|
|
354
|
+
checkoutChunkMaxEntries: ctx.env.NIMBUS_GIT_CHECKOUT_CHUNK_ENTRIES
|
|
355
|
+
? Number(ctx.env.NIMBUS_GIT_CHECKOUT_CHUNK_ENTRIES) || undefined
|
|
356
|
+
: undefined,
|
|
357
|
+
auth: {
|
|
358
|
+
username: ctx.env.GIT_USERNAME || '',
|
|
359
|
+
password: ctx.env.GIT_PASSWORD || ctx.env.GIT_TOKEN || '',
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
if (result.success) {
|
|
363
|
+
ctx.stdout.write(`\n[git] clone complete (${result.filesWritten} files, ` +
|
|
364
|
+
`${(result.bytesWritten / 1024).toFixed(1)}KB in ${(result.elapsed / 1000).toFixed(1)}s)\n`);
|
|
445
365
|
}
|
|
446
366
|
else {
|
|
447
|
-
ctx.
|
|
448
|
-
ctx.stdout.write(`Author: ${c.commit.author.name} <${c.commit.author.email}>\n`);
|
|
449
|
-
ctx.stdout.write(`Date: ${new Date(c.commit.author.timestamp * 1000).toDateString()}\n\n`);
|
|
450
|
-
ctx.stdout.write(` ${c.commit.message}\n\n`);
|
|
367
|
+
ctx.stderr.write(`\n[git] clone failed: ${result.error}\n`);
|
|
451
368
|
}
|
|
369
|
+
return result.success;
|
|
452
370
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
case 'branch': {
|
|
456
|
-
if (subArgs.length === 0 || subArgs[0] === '-a' || subArgs[0] === '--list') {
|
|
457
|
-
const branches = await git.listBranches({ fs, dir });
|
|
458
|
-
const current = await git.currentBranch({ fs, dir });
|
|
459
|
-
for (const b of branches) {
|
|
460
|
-
ctx.stdout.write(b === current ? `\x1b[32m* ${b}\x1b[0m\n` : ` ${b}\n`);
|
|
461
|
-
}
|
|
462
|
-
if (subArgs.includes('-a')) {
|
|
463
|
-
try {
|
|
464
|
-
const remotes = await git.listBranches({ fs, dir, remote: 'origin' });
|
|
465
|
-
for (const b of remotes)
|
|
466
|
-
ctx.stdout.write(` \x1b[31mremotes/origin/${b}\x1b[0m\n`);
|
|
467
|
-
}
|
|
468
|
-
catch { }
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
else if (subArgs.includes('-d') || subArgs.includes('-D')) {
|
|
472
|
-
const name = subArgs.find(a => !a.startsWith('-'));
|
|
473
|
-
if (name) {
|
|
474
|
-
await git.deleteBranch({ fs, dir, ref: name });
|
|
475
|
-
ctx.stdout.write(`Deleted branch ${name}\n`);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
else {
|
|
479
|
-
const name = subArgs[0];
|
|
480
|
-
await git.branch({ fs, dir, ref: name });
|
|
481
|
-
ctx.stdout.write(`Created branch ${name}\n`);
|
|
371
|
+
finally {
|
|
372
|
+
vfs.releaseExclusiveMutation(mutationLease.owner);
|
|
482
373
|
}
|
|
374
|
+
};
|
|
375
|
+
if (isBg) {
|
|
376
|
+
const task = doClone();
|
|
377
|
+
doCtx.waitUntil(task);
|
|
378
|
+
ctx.stdout.write('[git] clone running in background...\n');
|
|
483
379
|
return 0;
|
|
484
380
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
if (!ref) {
|
|
488
|
-
ctx.stderr.write('error: specify a branch\n');
|
|
489
|
-
return 1;
|
|
490
|
-
}
|
|
491
|
-
if (subArgs.includes('-b')) {
|
|
492
|
-
await git.branch({ fs, dir, ref });
|
|
493
|
-
await git.checkout({ fs, dir, ref });
|
|
494
|
-
ctx.stdout.write(`Switched to a new branch '${ref}'\n`);
|
|
495
|
-
}
|
|
496
|
-
else {
|
|
497
|
-
await git.checkout({ fs, dir, ref });
|
|
498
|
-
ctx.stdout.write(`Switched to branch '${ref}'\n`);
|
|
499
|
-
}
|
|
500
|
-
return 0;
|
|
381
|
+
else {
|
|
382
|
+
return (await doClone()) ? 0 : 1;
|
|
501
383
|
}
|
|
502
|
-
|
|
503
|
-
|
|
384
|
+
}
|
|
385
|
+
case 'status': {
|
|
386
|
+
const matrix = await git.statusMatrix({ fs, dir });
|
|
387
|
+
let clean = true;
|
|
388
|
+
for (const [filepath, head, workdir, stage] of matrix) {
|
|
389
|
+
if (head === workdir && workdir === stage)
|
|
390
|
+
continue;
|
|
391
|
+
clean = false;
|
|
392
|
+
if (head === 0 && workdir === 2 && stage === 0)
|
|
393
|
+
ctx.stdout.write(`\x1b[31m?? ${filepath}\x1b[0m\n`);
|
|
394
|
+
else if (head === 0 && stage === 2)
|
|
395
|
+
ctx.stdout.write(`\x1b[32mA ${filepath}\x1b[0m\n`);
|
|
396
|
+
else if (head === 1 && workdir === 2 && stage === 2)
|
|
397
|
+
ctx.stdout.write(`\x1b[32mM ${filepath}\x1b[0m\n`);
|
|
398
|
+
else if (head === 1 && workdir === 2 && stage === 1)
|
|
399
|
+
ctx.stdout.write(`\x1b[31m M ${filepath}\x1b[0m\n`);
|
|
400
|
+
else if (head === 1 && workdir === 0)
|
|
401
|
+
ctx.stdout.write(`\x1b[31m D ${filepath}\x1b[0m\n`);
|
|
402
|
+
else if (head === 1 && stage === 0)
|
|
403
|
+
ctx.stdout.write(`\x1b[32mD ${filepath}\x1b[0m\n`);
|
|
404
|
+
else
|
|
405
|
+
ctx.stdout.write(` ${filepath} [${head},${workdir},${stage}]\n`);
|
|
406
|
+
}
|
|
407
|
+
if (clean)
|
|
408
|
+
ctx.stdout.write('nothing to commit, working tree clean\n');
|
|
409
|
+
return 0;
|
|
410
|
+
}
|
|
411
|
+
case 'add': {
|
|
412
|
+
const paths = subArgs.filter(a => !a.startsWith('-'));
|
|
413
|
+
if (paths.length === 0 || paths.includes('.')) {
|
|
414
|
+
// Add all
|
|
504
415
|
const matrix = await git.statusMatrix({ fs, dir });
|
|
505
416
|
for (const [filepath, head, workdir, stage] of matrix) {
|
|
506
|
-
if (
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const lines = content.split('\n');
|
|
512
|
-
for (let i = 0; i < Math.min(lines.length, 50); i++) {
|
|
513
|
-
if (head === 0)
|
|
514
|
-
ctx.stdout.write(`\x1b[32m+${lines[i]}\x1b[0m\n`);
|
|
515
|
-
else
|
|
516
|
-
ctx.stdout.write(` ${lines[i]}\n`);
|
|
517
|
-
}
|
|
518
|
-
if (lines.length > 50)
|
|
519
|
-
ctx.stdout.write(`... (${lines.length - 50} more lines)\n`);
|
|
520
|
-
}
|
|
521
|
-
catch { }
|
|
522
|
-
ctx.stdout.write('\n');
|
|
417
|
+
if (head !== workdir || workdir !== stage) {
|
|
418
|
+
if (workdir === 0)
|
|
419
|
+
await git.remove({ fs, dir, filepath });
|
|
420
|
+
else
|
|
421
|
+
await git.add({ fs, dir, filepath });
|
|
523
422
|
}
|
|
524
423
|
}
|
|
525
|
-
return 0;
|
|
526
424
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
await git.
|
|
530
|
-
ctx.stdout.write(`Remote '${subArgs[1]}' added\n`);
|
|
531
|
-
}
|
|
532
|
-
else if (subArgs[0] === 'remove' || subArgs[0] === 'rm') {
|
|
533
|
-
await git.deleteRemote({ fs, dir, remote: subArgs[1] });
|
|
534
|
-
ctx.stdout.write(`Remote '${subArgs[1]}' removed\n`);
|
|
535
|
-
}
|
|
536
|
-
else {
|
|
537
|
-
const remotes = await git.listRemotes({ fs, dir });
|
|
538
|
-
for (const r of remotes) {
|
|
539
|
-
ctx.stdout.write(subArgs.includes('-v') ? `${r.remote}\t${r.url} (fetch)\n` : `${r.remote}\n`);
|
|
540
|
-
}
|
|
425
|
+
else {
|
|
426
|
+
for (const filepath of paths) {
|
|
427
|
+
await git.add({ fs, dir, filepath });
|
|
541
428
|
}
|
|
542
|
-
return 0;
|
|
543
429
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
ctx.
|
|
551
|
-
|
|
552
|
-
op: 'fetch',
|
|
553
|
-
pid: ctx.pid,
|
|
554
|
-
dir,
|
|
555
|
-
remote,
|
|
556
|
-
auth: {
|
|
557
|
-
username: ctx.env.GIT_USERNAME || '',
|
|
558
|
-
password: ctx.env.GIT_PASSWORD || ctx.env.GIT_TOKEN || '',
|
|
559
|
-
},
|
|
560
|
-
});
|
|
561
|
-
if (result.success) {
|
|
562
|
-
ctx.stdout.write(`\n[git] fetch complete (${result.filesWritten} files in ${(result.elapsed / 1000).toFixed(1)}s)\n`);
|
|
563
|
-
return 0;
|
|
564
|
-
}
|
|
565
|
-
else {
|
|
566
|
-
ctx.stderr.write(`\n[git] fetch failed: ${result.error}\n`);
|
|
567
|
-
return 1;
|
|
568
|
-
}
|
|
430
|
+
return 0;
|
|
431
|
+
}
|
|
432
|
+
case 'commit': {
|
|
433
|
+
const msgIdx = subArgs.indexOf('-m');
|
|
434
|
+
const message = msgIdx >= 0 ? subArgs[msgIdx + 1] : 'commit';
|
|
435
|
+
if (!message) {
|
|
436
|
+
ctx.stderr.write('error: empty commit message\n');
|
|
437
|
+
return 1;
|
|
569
438
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
author: getAuthor(ctx),
|
|
585
|
-
auth: {
|
|
586
|
-
username: ctx.env.GIT_USERNAME || '',
|
|
587
|
-
password: ctx.env.GIT_PASSWORD || ctx.env.GIT_TOKEN || '',
|
|
588
|
-
},
|
|
589
|
-
});
|
|
590
|
-
if (result.success) {
|
|
591
|
-
ctx.stdout.write(`\n[git] pull complete (${result.filesWritten} files in ${(result.elapsed / 1000).toFixed(1)}s)\n`);
|
|
592
|
-
return 0;
|
|
439
|
+
const sha = await git.commit({
|
|
440
|
+
fs, dir, message,
|
|
441
|
+
author: getAuthor(ctx),
|
|
442
|
+
});
|
|
443
|
+
ctx.stdout.write(`[${sha.slice(0, 7)}] ${message}\n`);
|
|
444
|
+
return 0;
|
|
445
|
+
}
|
|
446
|
+
case 'log': {
|
|
447
|
+
const maxCount = parseInt(getFlag(subArgs, '-n') || getFlag(subArgs, '--max-count') || '10');
|
|
448
|
+
const oneline = subArgs.includes('--oneline');
|
|
449
|
+
const commits = await git.log({ fs, dir, depth: maxCount });
|
|
450
|
+
for (const c of commits) {
|
|
451
|
+
if (oneline) {
|
|
452
|
+
ctx.stdout.write(`\x1b[33m${c.oid.slice(0, 7)}\x1b[0m ${c.commit.message.split('\n')[0]}\n`);
|
|
593
453
|
}
|
|
594
454
|
else {
|
|
595
|
-
ctx.
|
|
596
|
-
|
|
455
|
+
ctx.stdout.write(`\x1b[33mcommit ${c.oid}\x1b[0m\n`);
|
|
456
|
+
ctx.stdout.write(`Author: ${c.commit.author.name} <${c.commit.author.email}>\n`);
|
|
457
|
+
ctx.stdout.write(`Date: ${new Date(c.commit.author.timestamp * 1000).toDateString()}\n\n`);
|
|
458
|
+
ctx.stdout.write(` ${c.commit.message}\n\n`);
|
|
597
459
|
}
|
|
598
460
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
password: ctx.env.GIT_PASSWORD || ctx.env.GIT_TOKEN || '',
|
|
616
|
-
},
|
|
617
|
-
});
|
|
618
|
-
if (result.success) {
|
|
619
|
-
ctx.stdout.write(`\n[git] push complete (${(result.elapsed / 1000).toFixed(1)}s)\n`);
|
|
620
|
-
return 0;
|
|
621
|
-
}
|
|
622
|
-
else {
|
|
623
|
-
ctx.stderr.write(`\n[git] push failed: ${result.error}\n`);
|
|
624
|
-
return 1;
|
|
461
|
+
return 0;
|
|
462
|
+
}
|
|
463
|
+
case 'branch': {
|
|
464
|
+
if (subArgs.length === 0 || subArgs[0] === '-a' || subArgs[0] === '--list') {
|
|
465
|
+
const branches = await git.listBranches({ fs, dir });
|
|
466
|
+
const current = await git.currentBranch({ fs, dir });
|
|
467
|
+
for (const b of branches) {
|
|
468
|
+
ctx.stdout.write(b === current ? `\x1b[32m* ${b}\x1b[0m\n` : ` ${b}\n`);
|
|
469
|
+
}
|
|
470
|
+
if (subArgs.includes('-a')) {
|
|
471
|
+
try {
|
|
472
|
+
const remotes = await git.listBranches({ fs, dir, remote: 'origin' });
|
|
473
|
+
for (const b of remotes)
|
|
474
|
+
ctx.stdout.write(` \x1b[31mremotes/origin/${b}\x1b[0m\n`);
|
|
475
|
+
}
|
|
476
|
+
catch { }
|
|
625
477
|
}
|
|
626
478
|
}
|
|
627
|
-
|
|
628
|
-
const
|
|
629
|
-
if (
|
|
630
|
-
|
|
631
|
-
|
|
479
|
+
else if (subArgs.includes('-d') || subArgs.includes('-D')) {
|
|
480
|
+
const name = subArgs.find(a => !a.startsWith('-'));
|
|
481
|
+
if (name) {
|
|
482
|
+
await git.deleteBranch({ fs, dir, ref: name });
|
|
483
|
+
ctx.stdout.write(`Deleted branch ${name}\n`);
|
|
632
484
|
}
|
|
633
|
-
await git.merge({
|
|
634
|
-
fs, dir, theirs,
|
|
635
|
-
author: getAuthor(ctx),
|
|
636
|
-
});
|
|
637
|
-
ctx.stdout.write(`Merged ${theirs}\n`);
|
|
638
|
-
return 0;
|
|
639
485
|
}
|
|
640
|
-
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
486
|
+
else {
|
|
487
|
+
const name = subArgs[0];
|
|
488
|
+
await git.branch({ fs, dir, ref: name });
|
|
489
|
+
ctx.stdout.write(`Created branch ${name}\n`);
|
|
490
|
+
}
|
|
491
|
+
return 0;
|
|
492
|
+
}
|
|
493
|
+
case 'checkout': {
|
|
494
|
+
const ref = subArgs.find(a => !a.startsWith('-'));
|
|
495
|
+
if (!ref) {
|
|
496
|
+
ctx.stderr.write('error: specify a branch\n');
|
|
497
|
+
return 1;
|
|
498
|
+
}
|
|
499
|
+
if (subArgs.includes('-b')) {
|
|
500
|
+
await git.branch({ fs, dir, ref });
|
|
501
|
+
await git.checkout({ fs, dir, ref });
|
|
502
|
+
ctx.stdout.write(`Switched to a new branch '${ref}'\n`);
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
await git.checkout({ fs, dir, ref });
|
|
506
|
+
ctx.stdout.write(`Switched to branch '${ref}'\n`);
|
|
507
|
+
}
|
|
508
|
+
return 0;
|
|
509
|
+
}
|
|
510
|
+
case 'diff': {
|
|
511
|
+
// Simple diff: show unstaged changes
|
|
512
|
+
const matrix = await git.statusMatrix({ fs, dir });
|
|
513
|
+
for (const [filepath, head, workdir, stage] of matrix) {
|
|
514
|
+
if (workdir !== head || workdir !== stage) {
|
|
515
|
+
ctx.stdout.write(`\x1b[1mdiff --git a/${filepath} b/${filepath}\x1b[0m\n`);
|
|
516
|
+
try {
|
|
517
|
+
const raw = await fs.promises.readFile(dir + '/' + filepath);
|
|
518
|
+
const content = typeof raw === 'string' ? raw : dec.decode(raw);
|
|
519
|
+
const lines = content.split('\n');
|
|
520
|
+
for (let i = 0; i < Math.min(lines.length, 50); i++) {
|
|
521
|
+
if (head === 0)
|
|
522
|
+
ctx.stdout.write(`\x1b[32m+${lines[i]}\x1b[0m\n`);
|
|
523
|
+
else
|
|
524
|
+
ctx.stdout.write(` ${lines[i]}\n`);
|
|
656
525
|
}
|
|
657
|
-
|
|
526
|
+
if (lines.length > 50)
|
|
527
|
+
ctx.stdout.write(`... (${lines.length - 50} more lines)\n`);
|
|
658
528
|
}
|
|
529
|
+
catch { }
|
|
530
|
+
ctx.stdout.write('\n');
|
|
659
531
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
532
|
+
}
|
|
533
|
+
return 0;
|
|
534
|
+
}
|
|
535
|
+
case 'remote': {
|
|
536
|
+
if (subArgs[0] === 'add' && subArgs[1] && subArgs[2]) {
|
|
537
|
+
await git.addRemote({ fs, dir, remote: subArgs[1], url: subArgs[2] });
|
|
538
|
+
ctx.stdout.write(`Remote '${subArgs[1]}' added\n`);
|
|
539
|
+
}
|
|
540
|
+
else if (subArgs[0] === 'remove' || subArgs[0] === 'rm') {
|
|
541
|
+
await git.deleteRemote({ fs, dir, remote: subArgs[1] });
|
|
542
|
+
ctx.stdout.write(`Remote '${subArgs[1]}' removed\n`);
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
const remotes = await git.listRemotes({ fs, dir });
|
|
546
|
+
for (const r of remotes) {
|
|
547
|
+
ctx.stdout.write(subArgs.includes('-v') ? `${r.remote}\t${r.url} (fetch)\n` : `${r.remote}\n`);
|
|
663
548
|
}
|
|
664
|
-
|
|
549
|
+
}
|
|
550
|
+
return 0;
|
|
551
|
+
}
|
|
552
|
+
case 'fetch': {
|
|
553
|
+
const remote = subArgs[0] || 'origin';
|
|
554
|
+
if (!doCtx || !doEnv) {
|
|
555
|
+
ctx.stderr.write('[git] fetch requires DO ctx + env (internal configuration error)\n');
|
|
556
|
+
return 1;
|
|
557
|
+
}
|
|
558
|
+
ctx.stdout.write(`Fetching from ${remote}...\n`);
|
|
559
|
+
const result = await execGitNetwork(doCtx, doEnv, {
|
|
560
|
+
op: 'fetch',
|
|
561
|
+
pid: ctx.pid,
|
|
562
|
+
dir,
|
|
563
|
+
remote,
|
|
564
|
+
auth: {
|
|
565
|
+
username: ctx.env.GIT_USERNAME || '',
|
|
566
|
+
password: ctx.env.GIT_PASSWORD || ctx.env.GIT_TOKEN || '',
|
|
567
|
+
},
|
|
568
|
+
});
|
|
569
|
+
if (result.success) {
|
|
570
|
+
ctx.stdout.write(`\n[git] fetch complete (${result.filesWritten} files in ${(result.elapsed / 1000).toFixed(1)}s)\n`);
|
|
665
571
|
return 0;
|
|
666
572
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
573
|
+
else {
|
|
574
|
+
ctx.stderr.write(`\n[git] fetch failed: ${result.error}\n`);
|
|
575
|
+
return 1;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
case 'pull': {
|
|
579
|
+
const remote = subArgs[0] || 'origin';
|
|
580
|
+
const branch = subArgs[1] || await git.currentBranch({ fs, dir }) || 'main';
|
|
581
|
+
if (!doCtx || !doEnv) {
|
|
582
|
+
ctx.stderr.write('[git] pull requires DO ctx + env (internal configuration error)\n');
|
|
583
|
+
return 1;
|
|
584
|
+
}
|
|
585
|
+
ctx.stdout.write(`Pulling from ${remote}/${branch}...\n`);
|
|
586
|
+
const result = await execGitNetwork(doCtx, doEnv, {
|
|
587
|
+
op: 'pull',
|
|
588
|
+
pid: ctx.pid,
|
|
589
|
+
dir,
|
|
590
|
+
remote,
|
|
591
|
+
ref: branch,
|
|
592
|
+
author: getAuthor(ctx),
|
|
593
|
+
auth: {
|
|
594
|
+
username: ctx.env.GIT_USERNAME || '',
|
|
595
|
+
password: ctx.env.GIT_PASSWORD || ctx.env.GIT_TOKEN || '',
|
|
596
|
+
},
|
|
597
|
+
});
|
|
598
|
+
if (result.success) {
|
|
599
|
+
ctx.stdout.write(`\n[git] pull complete (${result.filesWritten} files in ${(result.elapsed / 1000).toFixed(1)}s)\n`);
|
|
683
600
|
return 0;
|
|
684
601
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
602
|
+
else {
|
|
603
|
+
ctx.stderr.write(`\n[git] pull failed: ${result.error}\n`);
|
|
604
|
+
return 1;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
case 'push': {
|
|
608
|
+
const remote = subArgs[0] || 'origin';
|
|
609
|
+
const branch = subArgs[1] || await git.currentBranch({ fs, dir }) || 'main';
|
|
610
|
+
if (!doCtx || !doEnv) {
|
|
611
|
+
ctx.stderr.write('[git] push requires DO ctx + env (internal configuration error)\n');
|
|
612
|
+
return 1;
|
|
613
|
+
}
|
|
614
|
+
ctx.stdout.write(`Pushing to ${remote}/${branch}...\n`);
|
|
615
|
+
const result = await execGitNetwork(doCtx, doEnv, {
|
|
616
|
+
op: 'push',
|
|
617
|
+
pid: ctx.pid,
|
|
618
|
+
dir,
|
|
619
|
+
remote,
|
|
620
|
+
ref: branch,
|
|
621
|
+
auth: {
|
|
622
|
+
username: ctx.env.GIT_USERNAME || '',
|
|
623
|
+
password: ctx.env.GIT_PASSWORD || ctx.env.GIT_TOKEN || '',
|
|
624
|
+
},
|
|
625
|
+
});
|
|
626
|
+
if (result.success) {
|
|
627
|
+
ctx.stdout.write(`\n[git] push complete (${(result.elapsed / 1000).toFixed(1)}s)\n`);
|
|
628
|
+
return 0;
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
ctx.stderr.write(`\n[git] push failed: ${result.error}\n`);
|
|
632
|
+
return 1;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
case 'merge': {
|
|
636
|
+
const theirs = subArgs[0];
|
|
637
|
+
if (!theirs) {
|
|
638
|
+
ctx.stderr.write('usage: git merge <branch>\n');
|
|
639
|
+
return 1;
|
|
640
|
+
}
|
|
641
|
+
await git.merge({
|
|
642
|
+
fs, dir, theirs,
|
|
643
|
+
author: getAuthor(ctx),
|
|
644
|
+
});
|
|
645
|
+
ctx.stdout.write(`Merged ${theirs}\n`);
|
|
646
|
+
return 0;
|
|
647
|
+
}
|
|
648
|
+
case 'reset': {
|
|
649
|
+
const hard = subArgs.includes('--hard');
|
|
650
|
+
const soft = subArgs.includes('--soft');
|
|
651
|
+
const ref = subArgs.find(a => !a.startsWith('-')) || 'HEAD';
|
|
652
|
+
const oid = await git.resolveRef({ fs, dir, ref });
|
|
653
|
+
// Move the current branch to the target OID
|
|
654
|
+
const branch = await git.currentBranch({ fs, dir });
|
|
655
|
+
if (branch) {
|
|
656
|
+
await git.writeRef({ fs, dir, ref: `refs/heads/${branch}`, value: oid, force: true });
|
|
657
|
+
}
|
|
658
|
+
if (!soft) {
|
|
659
|
+
// Reset index (--mixed behavior, also applies to --hard)
|
|
660
|
+
const matrix = await git.statusMatrix({ fs, dir });
|
|
661
|
+
for (const [filepath] of matrix) {
|
|
694
662
|
try {
|
|
695
|
-
|
|
696
|
-
ctx.stdout.write(`${val}\n`);
|
|
697
|
-
}
|
|
698
|
-
catch {
|
|
699
|
-
ctx.stderr.write(`config: key '${key}' not set\n`);
|
|
700
|
-
return 1;
|
|
663
|
+
await git.resetIndex({ fs, dir, filepath });
|
|
701
664
|
}
|
|
665
|
+
catch { }
|
|
702
666
|
}
|
|
703
|
-
|
|
704
|
-
|
|
667
|
+
}
|
|
668
|
+
if (hard) {
|
|
669
|
+
// Reset working tree to match the target
|
|
670
|
+
await git.checkout({ fs, dir, ref: oid, force: true });
|
|
671
|
+
}
|
|
672
|
+
ctx.stdout.write(`HEAD is now at ${oid.slice(0, 7)}\n`);
|
|
673
|
+
return 0;
|
|
674
|
+
}
|
|
675
|
+
case 'tag': {
|
|
676
|
+
if (subArgs.length === 0) {
|
|
677
|
+
const tags = await git.listTags({ fs, dir });
|
|
678
|
+
for (const t of tags)
|
|
679
|
+
ctx.stdout.write(t + '\n');
|
|
680
|
+
}
|
|
681
|
+
else if (subArgs.includes('-d')) {
|
|
682
|
+
const name = subArgs.find(a => !a.startsWith('-'));
|
|
683
|
+
if (name)
|
|
684
|
+
await git.deleteTag({ fs, dir, ref: name });
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
const name = subArgs[0];
|
|
688
|
+
await git.tag({ fs, dir, ref: name });
|
|
689
|
+
ctx.stdout.write(`Created tag ${name}\n`);
|
|
690
|
+
}
|
|
691
|
+
return 0;
|
|
692
|
+
}
|
|
693
|
+
case 'config': {
|
|
694
|
+
const key = subArgs.find(a => !a.startsWith('-'));
|
|
695
|
+
const value = subArgs[subArgs.indexOf(key || '') + 1];
|
|
696
|
+
if (key && value) {
|
|
697
|
+
const [section, ...rest] = key.split('.');
|
|
698
|
+
await git.setConfig({ fs, dir, path: key, value });
|
|
699
|
+
ctx.stdout.write(`${key}=${value}\n`);
|
|
700
|
+
}
|
|
701
|
+
else if (key) {
|
|
702
|
+
try {
|
|
703
|
+
const val = await git.getConfig({ fs, dir, path: key });
|
|
704
|
+
ctx.stdout.write(`${val}\n`);
|
|
705
|
+
}
|
|
706
|
+
catch {
|
|
707
|
+
ctx.stderr.write(`config: key '${key}' not set\n`);
|
|
705
708
|
return 1;
|
|
706
709
|
}
|
|
707
|
-
return 0;
|
|
708
710
|
}
|
|
709
|
-
|
|
710
|
-
ctx.stderr.write(
|
|
711
|
+
else {
|
|
712
|
+
ctx.stderr.write('usage: git config <key> [value]\n');
|
|
711
713
|
return 1;
|
|
714
|
+
}
|
|
715
|
+
return 0;
|
|
712
716
|
}
|
|
717
|
+
default:
|
|
718
|
+
ctx.stderr.write(`git: '${sub}' is not a git command. See 'git --help'.\n`);
|
|
719
|
+
return 1;
|
|
713
720
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
}
|
|
721
|
+
}
|
|
722
|
+
catch (e) {
|
|
723
|
+
ctx.stderr.write(`fatal: ${e?.message || e}\n`);
|
|
724
|
+
return 128;
|
|
725
|
+
}
|
|
719
726
|
}
|