feat(lanes): name the files behind a lane's uncommitted counts
GET /api/lanes/:id/git now returns `files`, the raw `git status --porcelain` lines the dirty/untracked counts were already parsed from, capped at 20 so a lane mid-`npm install` cannot ship thousands of paths through a polled endpoint. The lane card hovers them as the tooltip on its "N modified · N untracked" text, appending "… +N" when the cap bit. No new subprocess and no new endpoint — statusCounts was counting those lines and throwing them away.
This commit is contained in:
@@ -100,6 +100,8 @@ describe("worktree", () => {
|
||||
assert.equal(s.dirty, 1);
|
||||
assert.equal(s.untracked, 1); // node_modules is ignored, so it does not count
|
||||
assert.match(s.head, /^[0-9a-f]{7,40}$/);
|
||||
// The same lines the counts came from, so a caller can name the files.
|
||||
assert.deepEqual(s.files.sort(), [" M README.md", "?? scratch.txt"]);
|
||||
// The fixture has no remotes. Given the lane's base branch, unpushedCount
|
||||
// measures base..HEAD — this worktree has committed nothing of its own, so 0.
|
||||
assert.equal(await wt.unpushedCount(dir, "main"), 0);
|
||||
|
||||
+11
-3
@@ -16,6 +16,8 @@ const execFileAsync = promisify(execFile);
|
||||
|
||||
const LANES_ROOT = process.env.LANES_ROOT || path.join(os.homedir(), ".claude", "agenthub-lanes");
|
||||
const PROTECTED_BRANCHES = new Set(["main", "master"]);
|
||||
/** Most status lines `statusCounts` reports by name; the counts stay exact. */
|
||||
const STATUS_FILE_CAP = 20;
|
||||
|
||||
/**
|
||||
* Promisified git wrapper. On failure, throws an Error with err.git = { args, code, stderr }.
|
||||
@@ -549,11 +551,16 @@ async function removeWorktree(lane) {
|
||||
/**
|
||||
* Parse git status --porcelain to count dirty, untracked, and get HEAD commit.
|
||||
* Lines starting with ?? are untracked; others are dirty.
|
||||
*
|
||||
* `files` carries the same lines back verbatim so a caller can show WHICH
|
||||
* files the counts mean, capped because this rides a polled endpoint and a
|
||||
* lane mid-`npm install` can report thousands.
|
||||
*/
|
||||
async function statusCounts(dir) {
|
||||
const result = await git(dir, ["status", "--porcelain=v1", "--untracked-files=normal"]);
|
||||
let dirty = 0;
|
||||
let untracked = 0;
|
||||
const files = [];
|
||||
|
||||
for (const line of result.stdout.split("\n")) {
|
||||
if (!line.trim()) continue;
|
||||
@@ -562,13 +569,14 @@ async function statusCounts(dir) {
|
||||
} else {
|
||||
dirty++;
|
||||
}
|
||||
if (files.length < STATUS_FILE_CAP) files.push(line.trimEnd());
|
||||
}
|
||||
|
||||
// Get short commit hash
|
||||
const headResult = await git(dir, ["rev-parse", "--short", "HEAD"]);
|
||||
const head = headResult.stdout.trim();
|
||||
|
||||
return { dirty, untracked, head };
|
||||
return { dirty, untracked, head, files };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -581,10 +589,10 @@ async function statusCounts(dir) {
|
||||
* caller shows what git says rather than inventing a nicer word for it.
|
||||
*/
|
||||
async function gitFacts(dir) {
|
||||
const { dirty, untracked, head } = await statusCounts(dir);
|
||||
const { dirty, untracked, head, files } = await statusCounts(dir);
|
||||
const branch = (await git(dir, ["rev-parse", "--abbrev-ref", "HEAD"])).stdout.trim();
|
||||
const subject = (await git(dir, ["log", "-1", "--format=%s"])).stdout.trim();
|
||||
return { branch, head, subject, dirty, untracked };
|
||||
return { branch, head, subject, dirty, untracked, files };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -274,7 +274,7 @@ const paths = {
|
||||
responses: {
|
||||
200: {
|
||||
description:
|
||||
"available:true with {branch, head, subject, dirty, untracked}, or available:false alone.",
|
||||
"available:true with {branch, head, subject, dirty, untracked, files}, or available:false alone. files holds the raw `git status --porcelain` lines behind those counts, capped at 20; the counts themselves are never capped.",
|
||||
},
|
||||
404: { description: "Lane not found." },
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user