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