@buildautomaton/cli 0.1.33 → 0.1.34
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/dist/cli.js +354 -204
- package/dist/cli.js.map +4 -4
- package/dist/index.js +350 -200
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23962,7 +23962,7 @@ function installBridgeProcessResilience() {
|
|
|
23962
23962
|
}
|
|
23963
23963
|
|
|
23964
23964
|
// src/cli-version.ts
|
|
23965
|
-
var CLI_VERSION = "0.1.
|
|
23965
|
+
var CLI_VERSION = "0.1.34".length > 0 ? "0.1.34" : "0.0.0-dev";
|
|
23966
23966
|
|
|
23967
23967
|
// src/connection/heartbeat/constants.ts
|
|
23968
23968
|
var BRIDGE_APP_HEARTBEAT_INTERVAL_MS = 1e4;
|
|
@@ -25550,17 +25550,13 @@ function resolveSessionParentPathForAgentProcess(resolvedSessionParentPath) {
|
|
|
25550
25550
|
return getBridgeRoot();
|
|
25551
25551
|
}
|
|
25552
25552
|
|
|
25553
|
-
// src/git/session-git-queue.ts
|
|
25554
|
-
import { execFile as execFile7 } from "node:child_process";
|
|
25553
|
+
// src/git/snapshot/session-git-queue.ts
|
|
25555
25554
|
import { readFile, stat } from "node:fs/promises";
|
|
25556
|
-
import { promisify as promisify7 } from "node:util";
|
|
25557
25555
|
import * as path15 from "node:path";
|
|
25558
25556
|
|
|
25559
|
-
// src/git/pre-turn-snapshot.ts
|
|
25557
|
+
// src/git/snapshot/pre-turn-snapshot.ts
|
|
25560
25558
|
import * as fs14 from "node:fs";
|
|
25561
25559
|
import * as path14 from "node:path";
|
|
25562
|
-
import { execFile as execFile6 } from "node:child_process";
|
|
25563
|
-
import { promisify as promisify6 } from "node:util";
|
|
25564
25560
|
|
|
25565
25561
|
// src/git/discover-repos.ts
|
|
25566
25562
|
import * as fs13 from "node:fs";
|
|
@@ -30125,12 +30121,68 @@ function gitInstanceFactory(baseDir, options) {
|
|
|
30125
30121
|
init_git_response_error();
|
|
30126
30122
|
var simpleGit = gitInstanceFactory;
|
|
30127
30123
|
|
|
30124
|
+
// src/git/git-runtime.ts
|
|
30125
|
+
var activeGitChildProcesses = /* @__PURE__ */ new Set();
|
|
30126
|
+
function abortActiveGitChildProcesses() {
|
|
30127
|
+
for (const child of activeGitChildProcesses) {
|
|
30128
|
+
try {
|
|
30129
|
+
child.kill("SIGTERM");
|
|
30130
|
+
} catch {
|
|
30131
|
+
}
|
|
30132
|
+
}
|
|
30133
|
+
}
|
|
30134
|
+
var GitOperationAbortedError = class extends Error {
|
|
30135
|
+
constructor(message = "Git operation aborted") {
|
|
30136
|
+
super(message);
|
|
30137
|
+
this.name = "GitOperationAbortedError";
|
|
30138
|
+
}
|
|
30139
|
+
};
|
|
30140
|
+
function throwIfGitShutdownRequested() {
|
|
30141
|
+
if (isCliImmediateShutdownRequested()) {
|
|
30142
|
+
abortActiveGitChildProcesses();
|
|
30143
|
+
throw new GitOperationAbortedError();
|
|
30144
|
+
}
|
|
30145
|
+
}
|
|
30146
|
+
async function runGitTask(fn) {
|
|
30147
|
+
throwIfGitShutdownRequested();
|
|
30148
|
+
try {
|
|
30149
|
+
const result = await fn();
|
|
30150
|
+
throwIfGitShutdownRequested();
|
|
30151
|
+
return result;
|
|
30152
|
+
} catch (e) {
|
|
30153
|
+
if (isCliImmediateShutdownRequested()) {
|
|
30154
|
+
throw new GitOperationAbortedError();
|
|
30155
|
+
}
|
|
30156
|
+
throw e;
|
|
30157
|
+
}
|
|
30158
|
+
}
|
|
30159
|
+
async function yieldToEventLoop2() {
|
|
30160
|
+
throwIfGitShutdownRequested();
|
|
30161
|
+
await new Promise((resolve18) => {
|
|
30162
|
+
setImmediate(resolve18);
|
|
30163
|
+
});
|
|
30164
|
+
throwIfGitShutdownRequested();
|
|
30165
|
+
}
|
|
30166
|
+
async function forEachWithGitYield(items, fn) {
|
|
30167
|
+
for (let i = 0; i < items.length; i++) {
|
|
30168
|
+
if (i > 0 || items.length > 1) await yieldToEventLoop2();
|
|
30169
|
+
await fn(items[i], i);
|
|
30170
|
+
}
|
|
30171
|
+
}
|
|
30172
|
+
|
|
30128
30173
|
// src/git/cli-simple-git.ts
|
|
30129
30174
|
function cliSimpleGit(baseDir) {
|
|
30130
|
-
const git = simpleGit({
|
|
30175
|
+
const git = simpleGit({
|
|
30176
|
+
baseDir,
|
|
30177
|
+
trimmed: true,
|
|
30178
|
+
spawnOptions: {}
|
|
30179
|
+
});
|
|
30131
30180
|
git.outputHandler((command, stdout, stderr) => {
|
|
30132
30181
|
const trace = isCliTrace();
|
|
30133
30182
|
const onChunk = (label) => (chunk) => {
|
|
30183
|
+
if (isCliImmediateShutdownRequested()) {
|
|
30184
|
+
abortActiveGitChildProcesses();
|
|
30185
|
+
}
|
|
30134
30186
|
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
30135
30187
|
const line = text.replace(/\s+$/, "");
|
|
30136
30188
|
if (trace && line) {
|
|
@@ -30219,17 +30271,37 @@ async function discoverGitReposUnderRoot(rootPath) {
|
|
|
30219
30271
|
return out;
|
|
30220
30272
|
}
|
|
30221
30273
|
|
|
30222
|
-
// src/git/
|
|
30274
|
+
// src/git/git-exec.ts
|
|
30275
|
+
import { execFile as execFile6, spawn as spawn4 } from "node:child_process";
|
|
30276
|
+
import { promisify as promisify6 } from "node:util";
|
|
30223
30277
|
var execFileAsync5 = promisify6(execFile6);
|
|
30278
|
+
async function execGitFile(args, options) {
|
|
30279
|
+
throwIfGitShutdownRequested();
|
|
30280
|
+
try {
|
|
30281
|
+
const result = await execFileAsync5("git", args, {
|
|
30282
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
30283
|
+
...options
|
|
30284
|
+
});
|
|
30285
|
+
throwIfGitShutdownRequested();
|
|
30286
|
+
return {
|
|
30287
|
+
stdout: String(result.stdout ?? ""),
|
|
30288
|
+
stderr: String(result.stderr ?? "")
|
|
30289
|
+
};
|
|
30290
|
+
} catch (e) {
|
|
30291
|
+
if (isCliImmediateShutdownRequested()) {
|
|
30292
|
+
throw new GitOperationAbortedError();
|
|
30293
|
+
}
|
|
30294
|
+
throw e;
|
|
30295
|
+
}
|
|
30296
|
+
}
|
|
30297
|
+
|
|
30298
|
+
// src/git/snapshot/pre-turn-snapshot.ts
|
|
30224
30299
|
function snapshotsDirForCwd(agentCwd) {
|
|
30225
30300
|
return path14.join(agentCwd, ".buildautomaton", "snapshots");
|
|
30226
30301
|
}
|
|
30227
30302
|
async function gitStashCreate(repoRoot, log2) {
|
|
30228
30303
|
try {
|
|
30229
|
-
const { stdout } = await
|
|
30230
|
-
cwd: repoRoot,
|
|
30231
|
-
maxBuffer: 10 * 1024 * 1024
|
|
30232
|
-
});
|
|
30304
|
+
const { stdout } = await execGitFile(["stash", "create"], { cwd: repoRoot });
|
|
30233
30305
|
return stdout.trim();
|
|
30234
30306
|
} catch (e) {
|
|
30235
30307
|
log2(
|
|
@@ -30240,7 +30312,7 @@ async function gitStashCreate(repoRoot, log2) {
|
|
|
30240
30312
|
}
|
|
30241
30313
|
async function gitRun(repoRoot, args, log2, label) {
|
|
30242
30314
|
try {
|
|
30243
|
-
await
|
|
30315
|
+
await execGitFile(args, { cwd: repoRoot });
|
|
30244
30316
|
return { ok: true };
|
|
30245
30317
|
} catch (e) {
|
|
30246
30318
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -30274,10 +30346,10 @@ async function capturePreTurnSnapshot(options) {
|
|
|
30274
30346
|
return { ok: false, error: "No git repos to snapshot" };
|
|
30275
30347
|
}
|
|
30276
30348
|
const repos = [];
|
|
30277
|
-
|
|
30349
|
+
await forEachWithGitYield(repoRoots, async (root) => {
|
|
30278
30350
|
const stashSha = await gitStashCreate(root, log2);
|
|
30279
30351
|
repos.push({ path: root, stashSha });
|
|
30280
|
-
}
|
|
30352
|
+
});
|
|
30281
30353
|
const dir = snapshotsDirForCwd(agentCwd);
|
|
30282
30354
|
try {
|
|
30283
30355
|
fs14.mkdirSync(dir, { recursive: true });
|
|
@@ -30312,17 +30384,25 @@ async function applyPreTurnSnapshot(filePath, log2) {
|
|
|
30312
30384
|
if (!Array.isArray(data.repos)) {
|
|
30313
30385
|
return { ok: false, error: "Invalid snapshot file" };
|
|
30314
30386
|
}
|
|
30315
|
-
|
|
30316
|
-
|
|
30387
|
+
let applyError = null;
|
|
30388
|
+
await forEachWithGitYield(data.repos, async (r) => {
|
|
30389
|
+
if (applyError || !r.path) return;
|
|
30317
30390
|
const reset = await gitRun(r.path, ["reset", "--hard", "HEAD"], log2, "reset --hard");
|
|
30318
|
-
if (!reset.ok)
|
|
30391
|
+
if (!reset.ok) {
|
|
30392
|
+
applyError = reset;
|
|
30393
|
+
return;
|
|
30394
|
+
}
|
|
30319
30395
|
const clean = await gitRun(r.path, ["clean", "-fd"], log2, "clean -fd");
|
|
30320
|
-
if (!clean.ok)
|
|
30396
|
+
if (!clean.ok) {
|
|
30397
|
+
applyError = clean;
|
|
30398
|
+
return;
|
|
30399
|
+
}
|
|
30321
30400
|
if (r.stashSha) {
|
|
30322
30401
|
const ap = await gitRun(r.path, ["stash", "apply", r.stashSha], log2, "stash apply");
|
|
30323
|
-
if (!ap.ok)
|
|
30402
|
+
if (!ap.ok) applyError = ap;
|
|
30324
30403
|
}
|
|
30325
|
-
}
|
|
30404
|
+
});
|
|
30405
|
+
if (applyError?.ok === false) return applyError;
|
|
30326
30406
|
log2(`[snapshot] Restored pre-turn state for ${data.runId.slice(0, 8)}\u2026`);
|
|
30327
30407
|
return { ok: true };
|
|
30328
30408
|
}
|
|
@@ -30330,8 +30410,7 @@ function snapshotFilePath(agentCwd, runId) {
|
|
|
30330
30410
|
return path14.join(snapshotsDirForCwd(agentCwd), `${runId}.json`);
|
|
30331
30411
|
}
|
|
30332
30412
|
|
|
30333
|
-
// src/git/session-git-queue.ts
|
|
30334
|
-
var execFileAsync6 = promisify7(execFile7);
|
|
30413
|
+
// src/git/snapshot/session-git-queue.ts
|
|
30335
30414
|
var MAX_FULL_FILE_TEXT_BYTES = 512 * 1024;
|
|
30336
30415
|
async function readWorkspaceFileAsUtf8(absPath) {
|
|
30337
30416
|
try {
|
|
@@ -30360,35 +30439,28 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
|
|
|
30360
30439
|
return;
|
|
30361
30440
|
}
|
|
30362
30441
|
const multiRepo = data.repos.length > 1;
|
|
30363
|
-
|
|
30364
|
-
if (!repo.stashSha)
|
|
30442
|
+
await forEachWithGitYield(data.repos, async (repo) => {
|
|
30443
|
+
if (!repo.stashSha) return;
|
|
30365
30444
|
let namesRaw;
|
|
30366
30445
|
try {
|
|
30367
|
-
const { stdout } = await
|
|
30368
|
-
cwd: repo.path,
|
|
30369
|
-
maxBuffer: 10 * 1024 * 1024
|
|
30370
|
-
});
|
|
30446
|
+
const { stdout } = await execGitFile(["diff", "--name-only", repo.stashSha], { cwd: repo.path });
|
|
30371
30447
|
namesRaw = stdout;
|
|
30372
30448
|
} catch (e) {
|
|
30373
30449
|
log2(
|
|
30374
30450
|
`[session-git-queue] Git diff --name-only failed in ${repo.path}: ${e instanceof Error ? e.message : String(e)}`
|
|
30375
30451
|
);
|
|
30376
|
-
|
|
30452
|
+
return;
|
|
30377
30453
|
}
|
|
30378
30454
|
const lines = namesRaw.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
30379
30455
|
const slug = path15.basename(repo.path).replace(/[^\w.-]+/g, "_") || "repo";
|
|
30380
|
-
|
|
30381
|
-
if (rel.includes(".."))
|
|
30456
|
+
await forEachWithGitYield(lines, async (rel) => {
|
|
30457
|
+
if (rel.includes("..")) return;
|
|
30382
30458
|
try {
|
|
30383
|
-
const { stdout: patchContent } = await
|
|
30384
|
-
"git",
|
|
30459
|
+
const { stdout: patchContent } = await execGitFile(
|
|
30385
30460
|
["diff", "--no-color", repo.stashSha, "--", rel],
|
|
30386
|
-
{
|
|
30387
|
-
cwd: repo.path,
|
|
30388
|
-
maxBuffer: 50 * 1024 * 1024
|
|
30389
|
-
}
|
|
30461
|
+
{ cwd: repo.path }
|
|
30390
30462
|
);
|
|
30391
|
-
if (!patchContent.trim())
|
|
30463
|
+
if (!patchContent.trim()) return;
|
|
30392
30464
|
const displayPath = multiRepo ? `${slug}/${rel}` : rel;
|
|
30393
30465
|
const workspaceFilePath = path15.join(repo.path, rel);
|
|
30394
30466
|
const newText = await readWorkspaceFileAsUtf8(workspaceFilePath);
|
|
@@ -30405,8 +30477,8 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
|
|
|
30405
30477
|
`[session-git-queue] Git diff failed for ${rel}: ${e instanceof Error ? e.message : String(e)}`
|
|
30406
30478
|
);
|
|
30407
30479
|
}
|
|
30408
|
-
}
|
|
30409
|
-
}
|
|
30480
|
+
});
|
|
30481
|
+
});
|
|
30410
30482
|
}
|
|
30411
30483
|
|
|
30412
30484
|
// src/agents/acp/put-summarize-change-summaries.ts
|
|
@@ -30774,9 +30846,9 @@ __export(claude_code_acp_client_exports, {
|
|
|
30774
30846
|
});
|
|
30775
30847
|
|
|
30776
30848
|
// src/agents/acp/clients/detect-command-on-path.ts
|
|
30777
|
-
import { execFile as
|
|
30778
|
-
import { promisify as
|
|
30779
|
-
var
|
|
30849
|
+
import { execFile as execFile7 } from "node:child_process";
|
|
30850
|
+
import { promisify as promisify7 } from "node:util";
|
|
30851
|
+
var execFileAsync6 = promisify7(execFile7);
|
|
30780
30852
|
var COMMAND_ON_PATH_PROBE_TIMEOUT_MS = 750;
|
|
30781
30853
|
async function execFileShutdownAware(file2, args, timeoutMs) {
|
|
30782
30854
|
if (isCliImmediateShutdownRequested()) throw new Error("shutdown");
|
|
@@ -30786,7 +30858,7 @@ async function execFileShutdownAware(file2, args, timeoutMs) {
|
|
|
30786
30858
|
}, 50);
|
|
30787
30859
|
shutdownPoll.unref?.();
|
|
30788
30860
|
try {
|
|
30789
|
-
await
|
|
30861
|
+
await execFileAsync6(file2, args, { timeout: timeoutMs, signal: ac.signal });
|
|
30790
30862
|
} finally {
|
|
30791
30863
|
clearInterval(shutdownPoll);
|
|
30792
30864
|
}
|
|
@@ -30866,7 +30938,7 @@ __export(cursor_acp_client_exports, {
|
|
|
30866
30938
|
createCursorAcpClient: () => createCursorAcpClient,
|
|
30867
30939
|
detectLocalAgentPresence: () => detectLocalAgentPresence3
|
|
30868
30940
|
});
|
|
30869
|
-
import { spawn as
|
|
30941
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
30870
30942
|
import * as readline from "node:readline";
|
|
30871
30943
|
|
|
30872
30944
|
// src/agents/acp/format-session-update-kind-for-log.ts
|
|
@@ -30936,7 +31008,7 @@ async function createCursorAcpClient(options) {
|
|
|
30936
31008
|
} = options;
|
|
30937
31009
|
const dbgFs = process.env.BUILDAUTOMATON_DEBUG_ACP_FS === "1";
|
|
30938
31010
|
const isWindows = process.platform === "win32";
|
|
30939
|
-
const child =
|
|
31011
|
+
const child = spawn5(command[0], command.slice(1), {
|
|
30940
31012
|
cwd,
|
|
30941
31013
|
stdio: ["pipe", "pipe", "pipe"],
|
|
30942
31014
|
env: process.env,
|
|
@@ -32382,7 +32454,7 @@ import os8 from "node:os";
|
|
|
32382
32454
|
import * as fs17 from "node:fs";
|
|
32383
32455
|
import * as path20 from "node:path";
|
|
32384
32456
|
|
|
32385
|
-
// src/git/worktree-add.ts
|
|
32457
|
+
// src/git/worktrees/worktree-add.ts
|
|
32386
32458
|
async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch) {
|
|
32387
32459
|
const mainGit = cliSimpleGit(mainRepoPath);
|
|
32388
32460
|
await mainGit.raw(["worktree", "add", "-b", branch, worktreePath, "HEAD"]);
|
|
@@ -32487,7 +32559,7 @@ async function prepareNewSessionWorktrees(options) {
|
|
|
32487
32559
|
};
|
|
32488
32560
|
}
|
|
32489
32561
|
|
|
32490
|
-
// src/git/rename-branch.ts
|
|
32562
|
+
// src/git/branches/rename-branch.ts
|
|
32491
32563
|
async function gitRenameCurrentBranch(repoDir, newName) {
|
|
32492
32564
|
const g = cliSimpleGit(repoDir);
|
|
32493
32565
|
await g.raw(["branch", "-m", newName]);
|
|
@@ -32511,10 +32583,10 @@ async function renameSessionWorktreeBranches(paths, newBranch, log2) {
|
|
|
32511
32583
|
// src/worktrees/remove-session-worktrees.ts
|
|
32512
32584
|
import * as fs20 from "node:fs";
|
|
32513
32585
|
|
|
32514
|
-
// src/git/worktree-remove.ts
|
|
32586
|
+
// src/git/worktrees/worktree-remove.ts
|
|
32515
32587
|
import * as fs19 from "node:fs";
|
|
32516
32588
|
|
|
32517
|
-
// src/git/resolve-main-repo-from-git-file.ts
|
|
32589
|
+
// src/git/worktrees/resolve-main-repo-from-git-file.ts
|
|
32518
32590
|
import * as fs18 from "node:fs";
|
|
32519
32591
|
import * as path21 from "node:path";
|
|
32520
32592
|
function resolveMainRepoFromWorktreeGitFile(wt) {
|
|
@@ -32528,7 +32600,7 @@ function resolveMainRepoFromWorktreeGitFile(wt) {
|
|
|
32528
32600
|
return path21.dirname(gitDir);
|
|
32529
32601
|
}
|
|
32530
32602
|
|
|
32531
|
-
// src/git/worktree-remove.ts
|
|
32603
|
+
// src/git/worktrees/worktree-remove.ts
|
|
32532
32604
|
async function gitWorktreeRemoveForce(worktreePath) {
|
|
32533
32605
|
const mainRepo = resolveMainRepoFromWorktreeGitFile(worktreePath);
|
|
32534
32606
|
if (mainRepo) {
|
|
@@ -32554,7 +32626,88 @@ async function removeSessionWorktrees(paths, log2) {
|
|
|
32554
32626
|
}
|
|
32555
32627
|
}
|
|
32556
32628
|
|
|
32557
|
-
// src/git/
|
|
32629
|
+
// src/git/changes/lib/parse-git-status.ts
|
|
32630
|
+
function parseNameStatusLines(lines) {
|
|
32631
|
+
const m = /* @__PURE__ */ new Map();
|
|
32632
|
+
for (const line of lines) {
|
|
32633
|
+
if (!line.trim()) continue;
|
|
32634
|
+
const tabParts = line.split(" ");
|
|
32635
|
+
if (tabParts.length < 2) continue;
|
|
32636
|
+
const status = tabParts[0].trim();
|
|
32637
|
+
const code = status[0];
|
|
32638
|
+
if (code === "A") {
|
|
32639
|
+
m.set(tabParts[tabParts.length - 1], "added");
|
|
32640
|
+
} else if (code === "D") {
|
|
32641
|
+
m.set(tabParts[tabParts.length - 1], "removed");
|
|
32642
|
+
} else if (code === "R" || code === "C") {
|
|
32643
|
+
if (tabParts.length >= 3) m.set(tabParts[tabParts.length - 1], "modified");
|
|
32644
|
+
} else if (code === "M" || code === "U" || code === "T") {
|
|
32645
|
+
m.set(tabParts[tabParts.length - 1], "modified");
|
|
32646
|
+
}
|
|
32647
|
+
}
|
|
32648
|
+
return m;
|
|
32649
|
+
}
|
|
32650
|
+
function parseNumstatFirstLine(line) {
|
|
32651
|
+
const parts = line.split(" ");
|
|
32652
|
+
if (parts.length < 3) return null;
|
|
32653
|
+
const [a, d] = parts;
|
|
32654
|
+
const additions = a === "-" ? 0 : parseInt(String(a), 10) || 0;
|
|
32655
|
+
const deletions = d === "-" ? 0 : parseInt(String(d), 10) || 0;
|
|
32656
|
+
return { additions, deletions };
|
|
32657
|
+
}
|
|
32658
|
+
function parseNumstat(lines) {
|
|
32659
|
+
const m = /* @__PURE__ */ new Map();
|
|
32660
|
+
for (const line of lines) {
|
|
32661
|
+
if (!line.trim()) continue;
|
|
32662
|
+
const parts = line.split(" ");
|
|
32663
|
+
if (parts.length < 3) continue;
|
|
32664
|
+
const [a, d, p] = parts;
|
|
32665
|
+
const additions = a === "-" ? 0 : parseInt(String(a), 10) || 0;
|
|
32666
|
+
const deletions = d === "-" ? 0 : parseInt(String(d), 10) || 0;
|
|
32667
|
+
m.set(p, { additions, deletions });
|
|
32668
|
+
}
|
|
32669
|
+
return m;
|
|
32670
|
+
}
|
|
32671
|
+
async function numstatFromGitNoIndex(g, pathInRepo) {
|
|
32672
|
+
const devNull = process.platform === "win32" ? "NUL" : "/dev/null";
|
|
32673
|
+
try {
|
|
32674
|
+
const out = await g.raw(["diff", "--numstat", "--no-index", "--", devNull, pathInRepo]);
|
|
32675
|
+
const first2 = String(out).split("\n").find((l) => l.trim()) ?? "";
|
|
32676
|
+
return parseNumstatFirstLine(first2);
|
|
32677
|
+
} catch {
|
|
32678
|
+
return null;
|
|
32679
|
+
}
|
|
32680
|
+
}
|
|
32681
|
+
|
|
32682
|
+
// src/git/changes/lib/working-tree-changed-path-count.ts
|
|
32683
|
+
function workingTreeChangedPathCount(nameStatusLines, numstatLines, untrackedLines) {
|
|
32684
|
+
const kindByPath = parseNameStatusLines(nameStatusLines);
|
|
32685
|
+
const numByPath = parseNumstat(numstatLines);
|
|
32686
|
+
const paths = /* @__PURE__ */ new Set([...kindByPath.keys(), ...numByPath.keys()]);
|
|
32687
|
+
for (const p of untrackedLines.map((s) => s.trim()).filter(Boolean)) {
|
|
32688
|
+
paths.add(p);
|
|
32689
|
+
}
|
|
32690
|
+
return paths.size;
|
|
32691
|
+
}
|
|
32692
|
+
|
|
32693
|
+
// src/git/changes/count-working-tree-changed-files.ts
|
|
32694
|
+
async function countWorkingTreeChangedFilesForRepo(repoGitCwd) {
|
|
32695
|
+
return runGitTask(async () => {
|
|
32696
|
+
const g = cliSimpleGit(repoGitCwd);
|
|
32697
|
+
const [nameStatusRaw, numstatRaw, untrackedRaw] = await Promise.all([
|
|
32698
|
+
g.raw(["diff", "--name-status", "HEAD"]).catch(() => ""),
|
|
32699
|
+
g.raw(["diff", "HEAD", "--numstat"]).catch(() => ""),
|
|
32700
|
+
g.raw(["ls-files", "--others", "--exclude-standard"]).catch(() => "")
|
|
32701
|
+
]);
|
|
32702
|
+
return workingTreeChangedPathCount(
|
|
32703
|
+
String(nameStatusRaw).split("\n"),
|
|
32704
|
+
String(numstatRaw).split("\n"),
|
|
32705
|
+
String(untrackedRaw).split("\n")
|
|
32706
|
+
);
|
|
32707
|
+
});
|
|
32708
|
+
}
|
|
32709
|
+
|
|
32710
|
+
// src/git/commits/resolve-remote-tracking.ts
|
|
32558
32711
|
async function tryConfigGet(g, key) {
|
|
32559
32712
|
try {
|
|
32560
32713
|
const out = await g.raw(["config", "--get", key]);
|
|
@@ -32637,30 +32790,6 @@ async function resolveBaseShaForUnpushedCommits(g) {
|
|
|
32637
32790
|
if (!defaultRef) return null;
|
|
32638
32791
|
return revParseSafe(g, defaultRef);
|
|
32639
32792
|
}
|
|
32640
|
-
function parseLogShaDateSubjectLines(raw) {
|
|
32641
|
-
const out = [];
|
|
32642
|
-
for (const line of String(raw).split("\n")) {
|
|
32643
|
-
const l = line.trimEnd();
|
|
32644
|
-
if (!l.trim()) continue;
|
|
32645
|
-
const parts = l.split(" ");
|
|
32646
|
-
if (parts.length < 3) continue;
|
|
32647
|
-
const sha = parts[0].trim();
|
|
32648
|
-
const committedAt = parts[1].trim();
|
|
32649
|
-
const subject = parts.slice(2).join(" ").trim();
|
|
32650
|
-
if (!/^[0-9a-f]{7,40}$/i.test(sha)) continue;
|
|
32651
|
-
out.push({ sha, shortSha: sha.slice(0, 7), subject, committedAt });
|
|
32652
|
-
}
|
|
32653
|
-
return out;
|
|
32654
|
-
}
|
|
32655
|
-
async function gitLogNotReachableFromBase(g, baseSha, headSha) {
|
|
32656
|
-
if (baseSha === headSha) return [];
|
|
32657
|
-
try {
|
|
32658
|
-
const logOut = await g.raw(["log", "--format=%H %cI %s", `${baseSha}..${headSha}`]);
|
|
32659
|
-
return parseLogShaDateSubjectLines(logOut);
|
|
32660
|
-
} catch {
|
|
32661
|
-
return [];
|
|
32662
|
-
}
|
|
32663
|
-
}
|
|
32664
32793
|
async function commitsAheadOfRemoteTracking(repoDir) {
|
|
32665
32794
|
const g = cliSimpleGit(repoDir);
|
|
32666
32795
|
const headSha = await revParseSafe(g, "HEAD");
|
|
@@ -32675,44 +32804,41 @@ async function commitsAheadOfRemoteTracking(repoDir) {
|
|
|
32675
32804
|
return 0;
|
|
32676
32805
|
}
|
|
32677
32806
|
}
|
|
32807
|
+
|
|
32808
|
+
// src/git/status/working-tree-status.ts
|
|
32678
32809
|
async function getRepoWorkingTreeStatus(repoDir) {
|
|
32679
|
-
|
|
32680
|
-
|
|
32681
|
-
|
|
32682
|
-
|
|
32683
|
-
|
|
32684
|
-
}
|
|
32685
|
-
async function listUnpushedCommits(repoDir) {
|
|
32686
|
-
const g = cliSimpleGit(repoDir);
|
|
32687
|
-
const headSha = await revParseSafe(g, "HEAD");
|
|
32688
|
-
if (!headSha) return [];
|
|
32689
|
-
const baseSha = await resolveBaseShaForUnpushedCommits(g);
|
|
32690
|
-
if (!baseSha) return [];
|
|
32691
|
-
return gitLogNotReachableFromBase(g, baseSha, headSha);
|
|
32810
|
+
return runGitTask(async () => {
|
|
32811
|
+
const uncommittedFileCount = await countWorkingTreeChangedFilesForRepo(repoDir);
|
|
32812
|
+
const hasUncommittedChanges = uncommittedFileCount > 0;
|
|
32813
|
+
const ahead = await commitsAheadOfRemoteTracking(repoDir);
|
|
32814
|
+
return { hasUncommittedChanges, hasUnpushedCommits: ahead > 0, uncommittedFileCount };
|
|
32815
|
+
});
|
|
32692
32816
|
}
|
|
32693
32817
|
async function aggregateSessionPathsWorkingTreeStatus(paths) {
|
|
32694
32818
|
let hasUncommittedChanges = false;
|
|
32695
32819
|
let hasUnpushedCommits = false;
|
|
32696
|
-
|
|
32820
|
+
let uncommittedFileCount = 0;
|
|
32821
|
+
await forEachWithGitYield(paths, async (p) => {
|
|
32697
32822
|
const s = await getRepoWorkingTreeStatus(p);
|
|
32823
|
+
uncommittedFileCount += s.uncommittedFileCount;
|
|
32698
32824
|
if (s.hasUncommittedChanges) hasUncommittedChanges = true;
|
|
32699
32825
|
if (s.hasUnpushedCommits) hasUnpushedCommits = true;
|
|
32700
|
-
}
|
|
32701
|
-
return { hasUncommittedChanges, hasUnpushedCommits };
|
|
32826
|
+
});
|
|
32827
|
+
return { hasUncommittedChanges, hasUnpushedCommits, uncommittedFileCount };
|
|
32702
32828
|
}
|
|
32703
32829
|
async function pushAheadOfUpstreamForPaths(paths) {
|
|
32704
|
-
|
|
32830
|
+
await forEachWithGitYield(paths, async (p) => {
|
|
32705
32831
|
const g = cliSimpleGit(p);
|
|
32706
32832
|
const ahead = await commitsAheadOfRemoteTracking(p);
|
|
32707
|
-
if (ahead <= 0)
|
|
32833
|
+
if (ahead <= 0) return;
|
|
32708
32834
|
await g.push();
|
|
32709
|
-
}
|
|
32835
|
+
});
|
|
32710
32836
|
}
|
|
32711
32837
|
|
|
32712
|
-
// src/git/
|
|
32838
|
+
// src/git/changes/types.ts
|
|
32713
32839
|
var MAX_PATCH_CHARS = 35e4;
|
|
32714
32840
|
|
|
32715
|
-
// src/git/
|
|
32841
|
+
// src/git/changes/lib/repo-format.ts
|
|
32716
32842
|
function posixJoinDirFile(dir, file2) {
|
|
32717
32843
|
const d = dir === "." || dir === "" ? "" : dir.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
32718
32844
|
const f = file2.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
@@ -32766,63 +32892,80 @@ function formatRemoteDisplayLabel(remoteUrl) {
|
|
|
32766
32892
|
return `origin \xB7 ${hostPath}`;
|
|
32767
32893
|
}
|
|
32768
32894
|
|
|
32769
|
-
// src/git/
|
|
32895
|
+
// src/git/changes/get-working-tree-change-repo-details.ts
|
|
32770
32896
|
import * as path23 from "node:path";
|
|
32771
32897
|
|
|
32772
|
-
// src/git/
|
|
32773
|
-
function
|
|
32774
|
-
const
|
|
32775
|
-
for (const line of
|
|
32776
|
-
|
|
32777
|
-
|
|
32778
|
-
|
|
32779
|
-
|
|
32780
|
-
const
|
|
32781
|
-
|
|
32782
|
-
|
|
32783
|
-
|
|
32784
|
-
|
|
32785
|
-
} else if (code === "R" || code === "C") {
|
|
32786
|
-
if (tabParts.length >= 3) m.set(tabParts[tabParts.length - 1], "modified");
|
|
32787
|
-
} else if (code === "M" || code === "U" || code === "T") {
|
|
32788
|
-
m.set(tabParts[tabParts.length - 1], "modified");
|
|
32789
|
-
}
|
|
32898
|
+
// src/git/commits/lib/parse-log-lines.ts
|
|
32899
|
+
function parseLogShaDateSubjectLines(raw) {
|
|
32900
|
+
const out = [];
|
|
32901
|
+
for (const line of String(raw).split("\n")) {
|
|
32902
|
+
const l = line.trimEnd();
|
|
32903
|
+
if (!l.trim()) continue;
|
|
32904
|
+
const parts = l.split(" ");
|
|
32905
|
+
if (parts.length < 3) continue;
|
|
32906
|
+
const sha = parts[0].trim();
|
|
32907
|
+
const committedAt = parts[1].trim();
|
|
32908
|
+
const subject = parts.slice(2).join(" ").trim();
|
|
32909
|
+
if (!/^[0-9a-f]{7,40}$/i.test(sha)) continue;
|
|
32910
|
+
out.push({ sha, shortSha: sha.slice(0, 7), subject, committedAt });
|
|
32790
32911
|
}
|
|
32791
|
-
return
|
|
32792
|
-
}
|
|
32793
|
-
function parseNumstatFirstLine(line) {
|
|
32794
|
-
const parts = line.split(" ");
|
|
32795
|
-
if (parts.length < 3) return null;
|
|
32796
|
-
const [a, d] = parts;
|
|
32797
|
-
const additions = a === "-" ? 0 : parseInt(String(a), 10) || 0;
|
|
32798
|
-
const deletions = d === "-" ? 0 : parseInt(String(d), 10) || 0;
|
|
32799
|
-
return { additions, deletions };
|
|
32912
|
+
return out;
|
|
32800
32913
|
}
|
|
32801
|
-
|
|
32802
|
-
|
|
32803
|
-
|
|
32804
|
-
|
|
32805
|
-
|
|
32806
|
-
|
|
32807
|
-
|
|
32808
|
-
|
|
32809
|
-
|
|
32810
|
-
m.set(p, { additions, deletions });
|
|
32914
|
+
|
|
32915
|
+
// src/git/commits/list-unpushed-commits.ts
|
|
32916
|
+
async function gitLogNotReachableFromBase(g, baseSha, headSha) {
|
|
32917
|
+
if (baseSha === headSha) return [];
|
|
32918
|
+
try {
|
|
32919
|
+
const logOut = await g.raw(["log", "--format=%H %cI %s", `${baseSha}..${headSha}`]);
|
|
32920
|
+
return parseLogShaDateSubjectLines(logOut);
|
|
32921
|
+
} catch {
|
|
32922
|
+
return [];
|
|
32811
32923
|
}
|
|
32812
|
-
return m;
|
|
32813
32924
|
}
|
|
32814
|
-
async function
|
|
32815
|
-
const
|
|
32925
|
+
async function listUnpushedCommits(repoDir) {
|
|
32926
|
+
const g = cliSimpleGit(repoDir);
|
|
32927
|
+
const headSha = await revParseSafe(g, "HEAD");
|
|
32928
|
+
if (!headSha) return [];
|
|
32929
|
+
const baseSha = await resolveBaseShaForUnpushedCommits(g);
|
|
32930
|
+
if (!baseSha) return [];
|
|
32931
|
+
return gitLogNotReachableFromBase(g, baseSha, headSha);
|
|
32932
|
+
}
|
|
32933
|
+
|
|
32934
|
+
// src/git/commits/lib/sanitize-recent-commits-limit.ts
|
|
32935
|
+
var RECENT_COMMITS_LIMIT_MIN = 1;
|
|
32936
|
+
var RECENT_COMMITS_LIMIT_MAX = 50;
|
|
32937
|
+
var RECENT_COMMITS_LIMIT_DEFAULT = 2;
|
|
32938
|
+
function sanitizeRecentCommitsLimit(value) {
|
|
32939
|
+
const n = typeof value === "number" ? value : typeof value === "string" ? Number.parseInt(value.trim(), 10) : Number.NaN;
|
|
32940
|
+
if (!Number.isFinite(n)) return RECENT_COMMITS_LIMIT_DEFAULT;
|
|
32941
|
+
return Math.min(RECENT_COMMITS_LIMIT_MAX, Math.max(RECENT_COMMITS_LIMIT_MIN, Math.trunc(n)));
|
|
32942
|
+
}
|
|
32943
|
+
|
|
32944
|
+
// src/git/commits/list-recent-commits.ts
|
|
32945
|
+
async function listRecentCommits(repoDir, limitInput) {
|
|
32946
|
+
const limit = sanitizeRecentCommitsLimit(limitInput);
|
|
32947
|
+
const g = cliSimpleGit(repoDir);
|
|
32948
|
+
const headSha = await revParseSafe(g, "HEAD");
|
|
32949
|
+
if (!headSha) return { commits: [], hasMore: false };
|
|
32950
|
+
const unpushedSet = new Set((await listUnpushedCommits(repoDir)).map((c) => c.sha));
|
|
32816
32951
|
try {
|
|
32817
|
-
const
|
|
32818
|
-
const
|
|
32819
|
-
|
|
32952
|
+
const logOut = await g.raw(["log", "--format=%H %cI %s", `-n`, String(limit), "HEAD"]);
|
|
32953
|
+
const commits = parseLogShaDateSubjectLines(logOut).map((c) => ({
|
|
32954
|
+
...c,
|
|
32955
|
+
needsPush: unpushedSet.has(c.sha)
|
|
32956
|
+
}));
|
|
32957
|
+
let hasMore = false;
|
|
32958
|
+
if (commits.length === limit) {
|
|
32959
|
+
const nextOut = await g.raw(["log", "-n", "1", `--skip=${limit}`, "--format=%H", "HEAD"]);
|
|
32960
|
+
hasMore = /^[0-9a-f]{7,40}$/i.test(String(nextOut).trim());
|
|
32961
|
+
}
|
|
32962
|
+
return { commits, hasMore };
|
|
32820
32963
|
} catch {
|
|
32821
|
-
return
|
|
32964
|
+
return { commits: [], hasMore: false };
|
|
32822
32965
|
}
|
|
32823
32966
|
}
|
|
32824
32967
|
|
|
32825
|
-
// src/git/
|
|
32968
|
+
// src/git/changes/lib/patch-truncate.ts
|
|
32826
32969
|
function truncatePatch(s) {
|
|
32827
32970
|
if (s.length <= MAX_PATCH_CHARS) return s;
|
|
32828
32971
|
return `${s.slice(0, MAX_PATCH_CHARS)}
|
|
@@ -32830,7 +32973,7 @@ function truncatePatch(s) {
|
|
|
32830
32973
|
\u2026 (diff truncated)`;
|
|
32831
32974
|
}
|
|
32832
32975
|
|
|
32833
|
-
// src/git/
|
|
32976
|
+
// src/git/commits/list-changed-files-for-commit.ts
|
|
32834
32977
|
var EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
32835
32978
|
async function parentForCommitDiff(g, sha) {
|
|
32836
32979
|
try {
|
|
@@ -32856,7 +32999,7 @@ async function listChangedFilesForCommit(repoGitCwd, repoRelPath, commitSha) {
|
|
|
32856
32999
|
const paths = new Set([...kindByPath.keys(), ...numByPath.keys()].filter(Boolean));
|
|
32857
33000
|
const rows = [];
|
|
32858
33001
|
const normRel = repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
|
|
32859
|
-
|
|
33002
|
+
await forEachWithGitYield([...paths], async (pathInRepo) => {
|
|
32860
33003
|
const relLauncher = posixJoinDirFile(normRel, pathInRepo.replace(/\\/g, "/"));
|
|
32861
33004
|
const nums = numByPath.get(pathInRepo);
|
|
32862
33005
|
let additions = nums?.additions ?? 0;
|
|
@@ -32868,8 +33011,8 @@ async function listChangedFilesForCommit(repoGitCwd, repoRelPath, commitSha) {
|
|
|
32868
33011
|
else change = "modified";
|
|
32869
33012
|
}
|
|
32870
33013
|
rows.push({ pathRelLauncher: relLauncher, additions, deletions, change });
|
|
32871
|
-
}
|
|
32872
|
-
|
|
33014
|
+
});
|
|
33015
|
+
await forEachWithGitYield(rows, async (row) => {
|
|
32873
33016
|
let pathInRepo;
|
|
32874
33017
|
if (normRel === ".") {
|
|
32875
33018
|
pathInRepo = row.pathRelLauncher;
|
|
@@ -32881,16 +33024,16 @@ async function listChangedFilesForCommit(repoGitCwd, repoRelPath, commitSha) {
|
|
|
32881
33024
|
const raw = await g.raw(["diff", "-U20000", range, "--", pathInRepo]).catch(() => "");
|
|
32882
33025
|
const t = String(raw).trim();
|
|
32883
33026
|
row.patchContent = t ? truncatePatch(t) : void 0;
|
|
32884
|
-
}
|
|
33027
|
+
});
|
|
32885
33028
|
rows.sort((a, b) => a.pathRelLauncher.localeCompare(b.pathRelLauncher));
|
|
32886
33029
|
return rows;
|
|
32887
33030
|
}
|
|
32888
33031
|
|
|
32889
|
-
// src/git/
|
|
33032
|
+
// src/git/changes/list-changed-files-for-repo.ts
|
|
32890
33033
|
import * as fs22 from "node:fs";
|
|
32891
33034
|
import * as path22 from "node:path";
|
|
32892
33035
|
|
|
32893
|
-
// src/git/
|
|
33036
|
+
// src/git/changes/lib/count-lines.ts
|
|
32894
33037
|
import { createReadStream } from "node:fs";
|
|
32895
33038
|
import * as readline2 from "node:readline";
|
|
32896
33039
|
async function countTextFileLines(filePath) {
|
|
@@ -32911,7 +33054,7 @@ async function countTextFileLines(filePath) {
|
|
|
32911
33054
|
return lines;
|
|
32912
33055
|
}
|
|
32913
33056
|
|
|
32914
|
-
// src/git/
|
|
33057
|
+
// src/git/changes/hydrate-patch.ts
|
|
32915
33058
|
import * as fs21 from "node:fs";
|
|
32916
33059
|
var UNIFIED_HUNK_HEADER_RE = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
|
|
32917
33060
|
var MAX_HYDRATE_LINES_PER_GAP = 8e3;
|
|
@@ -33027,26 +33170,16 @@ async function hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, p
|
|
|
33027
33170
|
return truncatePatch(out.join("\n"));
|
|
33028
33171
|
}
|
|
33029
33172
|
|
|
33030
|
-
// src/git/
|
|
33173
|
+
// src/git/changes/unified-diff-for-file.ts
|
|
33031
33174
|
async function unifiedDiffForFile(repoCwd, pathInRepo, change) {
|
|
33032
33175
|
const g = cliSimpleGit(repoCwd);
|
|
33033
|
-
|
|
33034
|
-
|
|
33035
|
-
|
|
33036
|
-
|
|
33037
|
-
raw = await g.raw(["diff", "--no-index", "--", devNull, pathInRepo]);
|
|
33038
|
-
} else {
|
|
33039
|
-
raw = await g.raw(["diff", "HEAD", "--", pathInRepo]);
|
|
33040
|
-
}
|
|
33041
|
-
const t = String(raw).trim();
|
|
33042
|
-
if (!t) return void 0;
|
|
33043
|
-
return truncatePatch(t);
|
|
33044
|
-
} catch {
|
|
33045
|
-
return void 0;
|
|
33046
|
-
}
|
|
33176
|
+
const args = change === "added" ? ["diff", "--no-color", "HEAD", "--", pathInRepo] : change === "removed" ? ["diff", "--no-color", "HEAD", "--", pathInRepo] : ["diff", "--no-color", "HEAD", "--", pathInRepo];
|
|
33177
|
+
const raw = await g.raw([...args]).catch(() => "");
|
|
33178
|
+
const t = String(raw).trim();
|
|
33179
|
+
return t ? truncatePatch(t) : void 0;
|
|
33047
33180
|
}
|
|
33048
33181
|
|
|
33049
|
-
// src/git/
|
|
33182
|
+
// src/git/changes/list-changed-files-for-repo.ts
|
|
33050
33183
|
async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
|
|
33051
33184
|
const g = cliSimpleGit(repoGitCwd);
|
|
33052
33185
|
const [nameStatusRaw, numstatRaw, untrackedRaw] = await Promise.all([
|
|
@@ -33060,7 +33193,7 @@ async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
|
|
|
33060
33193
|
const untracked = String(untrackedRaw).split("\n").map((s) => s.trim()).filter(Boolean);
|
|
33061
33194
|
for (const p of untracked) paths.add(p);
|
|
33062
33195
|
const rows = [];
|
|
33063
|
-
|
|
33196
|
+
await forEachWithGitYield([...paths], async (pathInRepo) => {
|
|
33064
33197
|
const relLauncher = posixJoinDirFile(repoRelPath, pathInRepo.replace(/\\/g, "/"));
|
|
33065
33198
|
const repoFilePath = path22.join(repoGitCwd, pathInRepo);
|
|
33066
33199
|
const nums = numByPath.get(pathInRepo);
|
|
@@ -33090,9 +33223,9 @@ async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
|
|
|
33090
33223
|
else change = "modified";
|
|
33091
33224
|
}
|
|
33092
33225
|
rows.push({ pathRelLauncher: relLauncher, additions, deletions, change });
|
|
33093
|
-
}
|
|
33226
|
+
});
|
|
33094
33227
|
const normRel = repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
|
|
33095
|
-
|
|
33228
|
+
await forEachWithGitYield(rows, async (row) => {
|
|
33096
33229
|
let pathInRepo;
|
|
33097
33230
|
if (normRel === ".") {
|
|
33098
33231
|
pathInRepo = row.pathRelLauncher;
|
|
@@ -33107,11 +33240,11 @@ async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
|
|
|
33107
33240
|
patch = await hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, pathInRepo, row.change);
|
|
33108
33241
|
}
|
|
33109
33242
|
row.patchContent = patch;
|
|
33110
|
-
}
|
|
33243
|
+
});
|
|
33111
33244
|
return rows;
|
|
33112
33245
|
}
|
|
33113
33246
|
|
|
33114
|
-
// src/git/
|
|
33247
|
+
// src/git/changes/get-working-tree-change-repo-details.ts
|
|
33115
33248
|
function normRepoRel(p) {
|
|
33116
33249
|
const x = p.replace(/\\/g, "/").trim();
|
|
33117
33250
|
return x === "" ? "." : x;
|
|
@@ -33130,7 +33263,9 @@ async function getWorkingTreeChangeRepoDetails(options) {
|
|
|
33130
33263
|
throw new Error("commit sha is required for commit changes");
|
|
33131
33264
|
}
|
|
33132
33265
|
const basis = filter == null && basisInput.kind === "commit" ? { kind: "working" } : basisInput;
|
|
33133
|
-
for (
|
|
33266
|
+
for (let i = 0; i < options.commitTargetPaths.length; i++) {
|
|
33267
|
+
if (i > 0) await yieldToEventLoop2();
|
|
33268
|
+
const target = options.commitTargetPaths[i];
|
|
33134
33269
|
const t = path23.resolve(target);
|
|
33135
33270
|
if (!await isGitRepoDirectory(t)) continue;
|
|
33136
33271
|
const g = cliSimpleGit(t);
|
|
@@ -33164,7 +33299,10 @@ async function getWorkingTreeChangeRepoDetails(options) {
|
|
|
33164
33299
|
const files = basis.kind === "commit" ? await listChangedFilesForCommit(t, relForList, basis.sha.trim()) : await listChangedFilesForRepo(t, relForList);
|
|
33165
33300
|
const st = await g.status();
|
|
33166
33301
|
const hasUncommittedChanges = (st.files?.length ?? 0) > 0;
|
|
33167
|
-
const unpushedCommits = await
|
|
33302
|
+
const [unpushedCommits, recentCommitList] = await Promise.all([
|
|
33303
|
+
listUnpushedCommits(t),
|
|
33304
|
+
listRecentCommits(t, options.recentCommitsLimit)
|
|
33305
|
+
]);
|
|
33168
33306
|
out.push({
|
|
33169
33307
|
repoRelPath: norm,
|
|
33170
33308
|
repoDisplayName,
|
|
@@ -33174,6 +33312,8 @@ async function getWorkingTreeChangeRepoDetails(options) {
|
|
|
33174
33312
|
files,
|
|
33175
33313
|
hasUncommittedChanges,
|
|
33176
33314
|
unpushedCommits,
|
|
33315
|
+
recentCommits: recentCommitList.commits,
|
|
33316
|
+
recentCommitsHasMore: recentCommitList.hasMore,
|
|
33177
33317
|
changesView: basis.kind === "commit" ? "commit" : "working",
|
|
33178
33318
|
changesCommitSha: basis.kind === "commit" ? basis.sha.trim() : null
|
|
33179
33319
|
});
|
|
@@ -33182,7 +33322,7 @@ async function getWorkingTreeChangeRepoDetails(options) {
|
|
|
33182
33322
|
return out;
|
|
33183
33323
|
}
|
|
33184
33324
|
|
|
33185
|
-
// src/git/commit-and-push.ts
|
|
33325
|
+
// src/git/branches/commit-and-push.ts
|
|
33186
33326
|
async function gitCommitAllIfDirty(repoDir, message, options) {
|
|
33187
33327
|
const g = cliSimpleGit(repoDir);
|
|
33188
33328
|
const st = await g.status();
|
|
@@ -33606,7 +33746,8 @@ var SessionWorktreeManager = class {
|
|
|
33606
33746
|
sessionWorktreeRootPath: sessionWorkingTreeRelRoot,
|
|
33607
33747
|
legacyRepoNestedSessionLayout: legacyNested,
|
|
33608
33748
|
repoFilterRelPath: opts?.repoRelPath?.trim() ? opts.repoRelPath.trim() : null,
|
|
33609
|
-
basis: opts?.basis
|
|
33749
|
+
basis: opts?.basis,
|
|
33750
|
+
recentCommitsLimit: opts?.recentCommitsLimit
|
|
33610
33751
|
});
|
|
33611
33752
|
}
|
|
33612
33753
|
async pushSessionUpstream(sessionId) {
|
|
@@ -34136,7 +34277,7 @@ function pipedStdoutStderrFor(attemptStdio) {
|
|
|
34136
34277
|
}
|
|
34137
34278
|
|
|
34138
34279
|
// src/dev-servers/manager/shell-spawn/try-spawn-piped-via-sh.ts
|
|
34139
|
-
import { spawn as
|
|
34280
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
34140
34281
|
function trySpawnPipedViaSh(command, env, cwd, signal) {
|
|
34141
34282
|
const attempts = [
|
|
34142
34283
|
{ stdio: [devNullReadFd(), "pipe", "pipe"], endStdin: false },
|
|
@@ -34157,9 +34298,9 @@ function trySpawnPipedViaSh(command, env, cwd, signal) {
|
|
|
34157
34298
|
if (process.platform === "win32") {
|
|
34158
34299
|
opts.windowsHide = true;
|
|
34159
34300
|
const com = process.env.ComSpec || "cmd.exe";
|
|
34160
|
-
proc =
|
|
34301
|
+
proc = spawn6(com, ["/d", "/s", "/c", command], opts);
|
|
34161
34302
|
} else {
|
|
34162
|
-
proc =
|
|
34303
|
+
proc = spawn6("/bin/sh", ["-c", command], opts);
|
|
34163
34304
|
}
|
|
34164
34305
|
if (attempt.endStdin) {
|
|
34165
34306
|
proc.stdin?.end();
|
|
@@ -34179,7 +34320,7 @@ function trySpawnPipedViaSh(command, env, cwd, signal) {
|
|
|
34179
34320
|
}
|
|
34180
34321
|
|
|
34181
34322
|
// src/dev-servers/manager/shell-spawn/try-spawn-shell-true-piped.ts
|
|
34182
|
-
import { spawn as
|
|
34323
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
34183
34324
|
function trySpawnShellTruePiped(command, env, cwd, devNullFd, signal) {
|
|
34184
34325
|
try {
|
|
34185
34326
|
const opts = {
|
|
@@ -34192,7 +34333,7 @@ function trySpawnShellTruePiped(command, env, cwd, devNullFd, signal) {
|
|
|
34192
34333
|
if (process.platform === "win32") {
|
|
34193
34334
|
opts.windowsHide = true;
|
|
34194
34335
|
}
|
|
34195
|
-
return
|
|
34336
|
+
return spawn7(command, opts);
|
|
34196
34337
|
} catch (e) {
|
|
34197
34338
|
if (isSpawnEbadf(e)) return null;
|
|
34198
34339
|
throw e;
|
|
@@ -34200,7 +34341,7 @@ function trySpawnShellTruePiped(command, env, cwd, devNullFd, signal) {
|
|
|
34200
34341
|
}
|
|
34201
34342
|
|
|
34202
34343
|
// src/dev-servers/manager/shell-spawn/try-spawn-merged-log-file.ts
|
|
34203
|
-
import { spawn as
|
|
34344
|
+
import { spawn as spawn8 } from "node:child_process";
|
|
34204
34345
|
import fs28 from "node:fs";
|
|
34205
34346
|
import { tmpdir } from "node:os";
|
|
34206
34347
|
import path31 from "node:path";
|
|
@@ -34218,7 +34359,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
|
|
|
34218
34359
|
try {
|
|
34219
34360
|
let proc;
|
|
34220
34361
|
if (process.platform === "win32") {
|
|
34221
|
-
proc =
|
|
34362
|
+
proc = spawn8(process.env.ComSpec || "cmd.exe", ["/d", "/s", "/c", command], {
|
|
34222
34363
|
env,
|
|
34223
34364
|
cwd,
|
|
34224
34365
|
stdio,
|
|
@@ -34226,7 +34367,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
|
|
|
34226
34367
|
...signal ? { signal } : {}
|
|
34227
34368
|
});
|
|
34228
34369
|
} else {
|
|
34229
|
-
proc =
|
|
34370
|
+
proc = spawn8("/bin/sh", ["-c", command], { env, cwd, stdio, ...signal ? { signal } : {} });
|
|
34230
34371
|
}
|
|
34231
34372
|
fs28.closeSync(logFd);
|
|
34232
34373
|
return {
|
|
@@ -34247,7 +34388,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
|
|
|
34247
34388
|
}
|
|
34248
34389
|
|
|
34249
34390
|
// src/dev-servers/manager/shell-spawn/try-spawn-shell-script-log-redirect.ts
|
|
34250
|
-
import { spawn as
|
|
34391
|
+
import { spawn as spawn9 } from "node:child_process";
|
|
34251
34392
|
import fs29 from "node:fs";
|
|
34252
34393
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
34253
34394
|
import path32 from "node:path";
|
|
@@ -34270,7 +34411,7 @@ cd ${shSingleQuote(cwd)}
|
|
|
34270
34411
|
/bin/sh ${shSingleQuote(innerPath)} >>${shSingleQuote(logPath)} 2>&1
|
|
34271
34412
|
`
|
|
34272
34413
|
);
|
|
34273
|
-
const proc =
|
|
34414
|
+
const proc = spawn9("/bin/sh", [runnerPath], {
|
|
34274
34415
|
env,
|
|
34275
34416
|
cwd: tmpRoot,
|
|
34276
34417
|
stdio: "ignore",
|
|
@@ -34302,7 +34443,7 @@ CD /D ${q(cwd)}\r
|
|
|
34302
34443
|
${command} >> ${q(logPath)} 2>&1\r
|
|
34303
34444
|
`
|
|
34304
34445
|
);
|
|
34305
|
-
const proc =
|
|
34446
|
+
const proc = spawn9(com, ["/d", "/s", "/c", q(runnerPath)], {
|
|
34306
34447
|
env,
|
|
34307
34448
|
cwd: tmpRoot,
|
|
34308
34449
|
stdio: "ignore",
|
|
@@ -34323,7 +34464,7 @@ ${command} >> ${q(logPath)} 2>&1\r
|
|
|
34323
34464
|
}
|
|
34324
34465
|
|
|
34325
34466
|
// src/dev-servers/manager/shell-spawn/try-spawn-inherit.ts
|
|
34326
|
-
import { spawn as
|
|
34467
|
+
import { spawn as spawn10 } from "node:child_process";
|
|
34327
34468
|
function trySpawnInheritStdio(command, env, cwd, signal) {
|
|
34328
34469
|
const opts = {
|
|
34329
34470
|
env,
|
|
@@ -34335,9 +34476,9 @@ function trySpawnInheritStdio(command, env, cwd, signal) {
|
|
|
34335
34476
|
if (process.platform === "win32") {
|
|
34336
34477
|
opts.windowsHide = true;
|
|
34337
34478
|
const com = process.env.ComSpec || "cmd.exe";
|
|
34338
|
-
proc =
|
|
34479
|
+
proc = spawn10(com, ["/d", "/s", "/c", command], opts);
|
|
34339
34480
|
} else {
|
|
34340
|
-
proc =
|
|
34481
|
+
proc = spawn10("/bin/sh", ["-c", command], opts);
|
|
34341
34482
|
}
|
|
34342
34483
|
return { proc, pipedStdoutStderr: false };
|
|
34343
34484
|
}
|
|
@@ -35593,12 +35734,12 @@ function createBridgePromptSenders(deps, getWs) {
|
|
|
35593
35734
|
}
|
|
35594
35735
|
|
|
35595
35736
|
// src/agents/acp/from-bridge/bridge-prompt-preamble.ts
|
|
35596
|
-
import { execFile as
|
|
35597
|
-
import { promisify as
|
|
35598
|
-
var
|
|
35737
|
+
import { execFile as execFile8 } from "node:child_process";
|
|
35738
|
+
import { promisify as promisify8 } from "node:util";
|
|
35739
|
+
var execFileAsync7 = promisify8(execFile8);
|
|
35599
35740
|
async function readGitBranch(cwd) {
|
|
35600
35741
|
try {
|
|
35601
|
-
const { stdout } = await
|
|
35742
|
+
const { stdout } = await execFileAsync7("git", ["branch", "--show-current"], { cwd, maxBuffer: 64 * 1024 });
|
|
35602
35743
|
const b = stdout.trim();
|
|
35603
35744
|
return b || null;
|
|
35604
35745
|
} catch {
|
|
@@ -36308,7 +36449,8 @@ var handleSessionGitRequestMessage = (msg, deps) => {
|
|
|
36308
36449
|
reply({
|
|
36309
36450
|
ok: true,
|
|
36310
36451
|
hasUncommittedChanges: r.hasUncommittedChanges,
|
|
36311
|
-
hasUnpushedCommits: r.hasUnpushedCommits
|
|
36452
|
+
hasUnpushedCommits: r.hasUnpushedCommits,
|
|
36453
|
+
uncommittedFileCount: r.uncommittedFileCount
|
|
36312
36454
|
});
|
|
36313
36455
|
return;
|
|
36314
36456
|
}
|
|
@@ -36322,7 +36464,12 @@ var handleSessionGitRequestMessage = (msg, deps) => {
|
|
|
36322
36464
|
return;
|
|
36323
36465
|
}
|
|
36324
36466
|
}
|
|
36325
|
-
const
|
|
36467
|
+
const recentCommitsLimit = typeof msg.changesRecentCommitsLimit === "number" || typeof msg.changesRecentCommitsLimit === "string" ? msg.changesRecentCommitsLimit : void 0;
|
|
36468
|
+
const opts = repoRel && view === "commit" && commitSha ? {
|
|
36469
|
+
repoRelPath: repoRel,
|
|
36470
|
+
basis: { kind: "commit", sha: commitSha },
|
|
36471
|
+
recentCommitsLimit
|
|
36472
|
+
} : repoRel ? { repoRelPath: repoRel, basis: { kind: "working" }, recentCommitsLimit } : recentCommitsLimit !== void 0 ? { recentCommitsLimit } : void 0;
|
|
36326
36473
|
const repos = await deps.sessionWorktreeManager.getSessionWorkingTreeChangeDetails(sessionId, opts);
|
|
36327
36474
|
reply({
|
|
36328
36475
|
ok: true,
|
|
@@ -36340,7 +36487,8 @@ var handleSessionGitRequestMessage = (msg, deps) => {
|
|
|
36340
36487
|
reply({
|
|
36341
36488
|
ok: true,
|
|
36342
36489
|
hasUncommittedChanges: st2.hasUncommittedChanges,
|
|
36343
|
-
hasUnpushedCommits: st2.hasUnpushedCommits
|
|
36490
|
+
hasUnpushedCommits: st2.hasUnpushedCommits,
|
|
36491
|
+
uncommittedFileCount: st2.uncommittedFileCount
|
|
36344
36492
|
});
|
|
36345
36493
|
return;
|
|
36346
36494
|
}
|
|
@@ -36365,7 +36513,8 @@ var handleSessionGitRequestMessage = (msg, deps) => {
|
|
|
36365
36513
|
reply({
|
|
36366
36514
|
ok: true,
|
|
36367
36515
|
hasUncommittedChanges: st.hasUncommittedChanges,
|
|
36368
|
-
hasUnpushedCommits: st.hasUnpushedCommits
|
|
36516
|
+
hasUnpushedCommits: st.hasUnpushedCommits,
|
|
36517
|
+
uncommittedFileCount: st.uncommittedFileCount
|
|
36369
36518
|
});
|
|
36370
36519
|
} catch (e) {
|
|
36371
36520
|
reply({ ok: false, error: e instanceof Error ? e.message : String(e) });
|
|
@@ -37344,6 +37493,7 @@ async function runConnectedBridge(options, restartWithoutAuth) {
|
|
|
37344
37493
|
let bridgeClose = null;
|
|
37345
37494
|
const onSignal = (kind) => {
|
|
37346
37495
|
requestCliImmediateShutdown();
|
|
37496
|
+
abortActiveGitChildProcesses();
|
|
37347
37497
|
cleanupKeyCommand?.();
|
|
37348
37498
|
logImmediate(
|
|
37349
37499
|
kind === "interrupt" ? "Keyboard interrupt (Ctrl+C) \u2014 stopping\u2026" : "Stop requested \u2014 shutting down\u2026"
|