feat(lanes): full-width Changes, Stage all, one-read expand-all
- The Changes panel's width control gains a full-width step (narrow → 50/50 → 70/30 → full → closed); the console beside it is hidden with CSS, never unmounted. - "Expand all folders" is one GET /api/lanes/:id/git/tree-all (one `git ls-files`, every level shaped like /git/tree) instead of one request and one git process per folder. - Overview: a Stage all button and the staged-only commit box sit above the file list and stay pinned while it scrolls; the Files finder is pinned too.
This commit is contained in:
@@ -19,6 +19,7 @@ const {
|
||||
listCommits,
|
||||
listPaths,
|
||||
listTree,
|
||||
listTreeAll,
|
||||
parseUnifiedDiff,
|
||||
readFileAt,
|
||||
} = require("../lib/git-diff");
|
||||
@@ -456,6 +457,22 @@ describe("listCommits / readFileAt / listPaths / listTree", () => {
|
||||
);
|
||||
await assert.rejects(() => listTree(LANE, { subdir: "../.." }), { code: "EBADPATH" });
|
||||
});
|
||||
|
||||
it("reads every level of the tree in one pass, grouped like listTree", async () => {
|
||||
write(LANE, "src/api/x.ts", "x\n");
|
||||
write(LANE, "src/main.ts", "m\n");
|
||||
gc(LANE, "add", "-A");
|
||||
gc(LANE, "commit", "-qm", "src");
|
||||
|
||||
const all = await listTreeAll(LANE);
|
||||
assert.equal(all.truncated, false);
|
||||
// Each level equals what the one-level read returns for it.
|
||||
for (const level of ["", "src", "src/api"]) {
|
||||
const one = await listTree(LANE, { subdir: level });
|
||||
assert.deepEqual(all.levels[level], one.entries, level);
|
||||
}
|
||||
assert.deepEqual(Object.keys(all.levels).sort(), ["", "src", "src/api"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("runGitAction — per-file staging, discard and branches", () => {
|
||||
|
||||
@@ -785,12 +785,50 @@ async function listTree(dir, { subdir = "" } = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Every level of the tracked tree from ONE `git ls-files`, keyed by directory
|
||||
* path ("" is the root), each level shaped and capped exactly like
|
||||
* `listTree`'s. "Expand all folders" used to be one request — one ls-files —
|
||||
* per folder, which on a large repo is thousands of git processes.
|
||||
*/
|
||||
async function listTreeAll(dir) {
|
||||
const { all, cut } = await lsFiles(dir, []);
|
||||
const byDir = new Map();
|
||||
const level = (d) => {
|
||||
let l = byDir.get(d);
|
||||
if (!l) byDir.set(d, (l = { dirs: new Set(), files: [] }));
|
||||
return l;
|
||||
};
|
||||
level("");
|
||||
for (const p of all) {
|
||||
const parts = p.split("/");
|
||||
for (let i = 0; i < parts.length - 1; i++) {
|
||||
level(parts.slice(0, i).join("/")).dirs.add(parts[i]);
|
||||
}
|
||||
const parent = parts.slice(0, -1).join("/");
|
||||
level(parent).files.push({ name: parts[parts.length - 1], path: p, dir: false });
|
||||
}
|
||||
const levels = {};
|
||||
let truncated = cut;
|
||||
for (const [d, { dirs, files }] of byDir) {
|
||||
const prefix = d ? `${d}/` : "";
|
||||
const entries = [
|
||||
...[...dirs].sort().map((name) => ({ name, path: `${prefix}${name}`, dir: true })),
|
||||
...files.sort((a, b) => a.name.localeCompare(b.name)),
|
||||
];
|
||||
if (entries.length > MAX_PATHS) truncated = true;
|
||||
levels[d] = entries.slice(0, MAX_PATHS);
|
||||
}
|
||||
return { levels, truncated };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
collectChanges,
|
||||
laneStatus,
|
||||
listCommits,
|
||||
listPaths,
|
||||
listTree,
|
||||
listTreeAll,
|
||||
parseNumstat,
|
||||
parsePorcelainZ,
|
||||
parseUnifiedDiff,
|
||||
|
||||
+19
-1
@@ -41,7 +41,14 @@ const {
|
||||
resolveBase,
|
||||
slugify,
|
||||
} = require("../lib/worktree");
|
||||
const { collectChanges, listCommits, listPaths, listTree, readFileAt } = require("../lib/git-diff");
|
||||
const {
|
||||
collectChanges,
|
||||
listCommits,
|
||||
listPaths,
|
||||
listTree,
|
||||
listTreeAll,
|
||||
readFileAt,
|
||||
} = require("../lib/git-diff");
|
||||
const { runGitAction } = require("../lib/git-ops");
|
||||
const { withLaneLock } = require("../lib/lane-lock");
|
||||
const { checkSync, mergeSync, continueSync } = require("../lib/lane-sync");
|
||||
@@ -859,6 +866,17 @@ router.get("/:id/git/ls", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/** Every level of the tracked tree in one read, for "expand all folders". */
|
||||
router.get("/:id/git/tree-all", async (req, res) => {
|
||||
const lane = laneOr404(req, res);
|
||||
if (!lane) return;
|
||||
try {
|
||||
res.json({ available: true, ...(await listTreeAll(lane.cwd)) });
|
||||
} catch {
|
||||
res.json({ available: false, levels: {}, truncated: false });
|
||||
}
|
||||
});
|
||||
|
||||
/** One directory level of tracked files, for the file explorer. */
|
||||
router.get("/:id/git/tree", async (req, res) => {
|
||||
const lane = laneOr404(req, res);
|
||||
|
||||
Reference in New Issue
Block a user