feat(lanes,git): publish a repo with no remote, and read a repo with no commits
What broke: a lane on a fresh `git init` repo had no working git surface.
`statusCounts` ran `rev-parse --short HEAD` unguarded, so `gitFacts` threw
and `GET /api/lanes/:id/git` answered `{available:false}` — the Changes
panel showed no header, and `commit`/`switch` 500'd through `isDirty`.
Add lane in Worktree mode accepted such a repo and left a `failed` row
behind, and a repo with commits but no remote had a permanently disabled
Push with no way to give it one short of a terminal.
What changed:
- worktree.js: an unborn HEAD is a normal state. `statusCounts` reports
`head: null`; `gitFacts` reads the branch via `symbolic-ref`, an empty
subject, and a new `unborn` field.
- git-ops.js: an 11th write, `publish` — `remote add origin -- <url>` then
`push -u origin HEAD`, only with a commit (ENOCOMMITS) and NO remote at
all (EREMOTEEXISTS, so it never re-points one), the url allowlisted by
`validateRemoteUrl` (https without userinfo, ssh://, scp-like; refuses
local paths, file://, ext::, fd::, git://, http://, leading `-`:
EBADURL), and `origin` removed again when the push fails.
- POST /api/lanes/worktree answers 400 EEMPTYREPO for a repo with no
commits instead of creating a row that can only fail.
- LaneGitBar: unborn repos show "no commits yet" and a commit-first hint;
no remote offers Publish… with an inline URL box; a remote but no
upstream relabels Push as "Publish branch". AddLaneModal disables Add
on an empty repo and offers "Use as a Repo lane".
- The two tests that used an unborn repo to force a provisioning failure
now use a `feat` ref clash, since that repo is refused up front.
- CLAUDE.md rule reworded to "the boring eleven" with publish's guards;
docs/API.md, docs/LANES.md, ARCHITECTURE.md, server/README.md, openapi.
This commit is contained in:
@@ -1554,6 +1554,11 @@ describe("/api/lanes tool tests", () => {
|
||||
const fs = require("fs");
|
||||
fs.mkdirSync(sourceRepo, { recursive: true });
|
||||
require("child_process").execSync("git init", { cwd: sourceRepo });
|
||||
// A worktree needs a commit to branch from (an unborn repo is EEMPTYREPO).
|
||||
require("child_process").execSync(
|
||||
"git -c user.email=t@h -c user.name=t commit -q --allow-empty -m init",
|
||||
{ cwd: sourceRepo }
|
||||
);
|
||||
|
||||
const res = await request("POST", "/api/lanes/worktree", {
|
||||
sourceRepo,
|
||||
@@ -1574,6 +1579,24 @@ describe("/api/lanes tool tests", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("POST /api/lanes/worktree refuses a repo with no commits, creating no row", async () => {
|
||||
const sourceRepo = require("path").join(
|
||||
require("os").tmpdir(),
|
||||
`agenthub-worktree-empty-${Date.now()}`
|
||||
);
|
||||
const fs = require("fs");
|
||||
fs.mkdirSync(sourceRepo, { recursive: true });
|
||||
require("child_process").execSync("git init -q", { cwd: sourceRepo });
|
||||
const before = (await request("GET", "/api/lanes")).body.lanes.length;
|
||||
|
||||
const res = await request("POST", "/api/lanes/worktree", { sourceRepo, slug: "empty" });
|
||||
|
||||
assert.equal(res.status, 400);
|
||||
assert.equal(res.body.error.code, "EEMPTYREPO");
|
||||
assert.equal((await request("GET", "/api/lanes")).body.lanes.length, before);
|
||||
fs.rmSync(sourceRepo, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("POST /api/lanes/worktree defaults to 'claude' tool when tool is not specified", async () => {
|
||||
const sourceRepo = require("path").join(
|
||||
require("os").tmpdir(),
|
||||
@@ -1582,6 +1605,11 @@ describe("/api/lanes tool tests", () => {
|
||||
const fs = require("fs");
|
||||
fs.mkdirSync(sourceRepo, { recursive: true });
|
||||
require("child_process").execSync("git init", { cwd: sourceRepo });
|
||||
// A worktree needs a commit to branch from (an unborn repo is EEMPTYREPO).
|
||||
require("child_process").execSync(
|
||||
"git -c user.email=t@h -c user.name=t commit -q --allow-empty -m init",
|
||||
{ cwd: sourceRepo }
|
||||
);
|
||||
|
||||
const res = await request("POST", "/api/lanes/worktree", {
|
||||
sourceRepo,
|
||||
|
||||
Reference in New Issue
Block a user