2cc492bb88
GET /git/refs lists local branches (upstream, ahead/behind, last commit, merged, held by a worktree, protected), remote branches grouped by remote and tags (target, annotated message/tagger/date). New writes in server/lib/git-refs.js, dispatched from git-ops.js: branch-rename, branch-upstream (set/unset), tag-create (light/annotated), tag-delete (confirm), tag-push (one tag to origin, no --tags, no force), and branch-create with a looked-up from + switch on a clean tree. Every ref is looked up, never passed through; repo-wide writes are repo-root only (ESUBDIR) and refused during any operation (EMERGING). Client: git/BranchManager + TagManager dialog opened from the git bar's tags icon, en+vi (gitRefs namespace), keyboard navigable. # Conflicts: # CLAUDE.md # client/src/components/lanes/LaneGitBar.tsx # docs/API.md # server/__tests__/lanes-api.test.js # server/routes/lanes.js
51 lines
3.9 KiB
JavaScript
51 lines
3.9 KiB
JavaScript
/**
|
|
* @file OpenAPI fragment for the git bar's Branches panel (server/lib/git-refs.js):
|
|
* the `GET /api/lanes/{id}/git/refs` read, plus `extend()`, which adds the
|
|
* branch & tag writes to the existing `POST /api/lanes/{id}/git/{action}`
|
|
* entry in place (its enum and description) rather than replacing that entry.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
const { REF_ACTIONS } = require("../lib/git-refs");
|
|
|
|
const REF_ACTIONS_DOC =
|
|
" Branch & tag tools (server/lib/git-refs.js; every ref named in the body is LOOKED UP in refs/heads, refs/remotes or refs/tags — or a hex sha verified as a commit — and git gets the full refname, never the request's text; every write here except branch-create runs only from a repo-root lane (400 ESUBDIR) and never while a merge, cherry-pick, revert or rebase is open (409 EMERGING)): branch-create also takes {from} (a local/remote branch, a tag, or a commit sha; 400 EBADREF, 400 EAMBIGUOUS when a branch and a tag share the name — spell out refs/tags/x) and {switch:true} (clean tree only, 400 EDIRTY); branch-rename {branch, name} (`git branch -m`; refuses main/master/development/develop 400 EPROTECTED, an existing name 400 EBRANCHEXISTS, a name `check-ref-format --branch` rejects 400 EBADBRANCH); branch-upstream {branch, upstream} sets it to a fetched remote branch (`origin/x`, 400 ENOBRANCH otherwise) and {branch} with no upstream unsets it (400 ENOUPSTREAM when none); tag-create {tag, message?, from?} (annotated when a message is given; on HEAD or a looked-up start point; 400 EBADTAG / ETAGEXISTS / ENOCOMMITS); tag-delete {tag, confirm:true} (irreversible: 400 ECONFIRM without it, 400 ENOTAG); tag-push {tag, credentials?} pushes ONE tag `refs/tags/<tag>:refs/tags/<tag>` to origin (else the only remote, 400 ENOREMOTE) — no --tags, no force, so a remote tag at another commit is a 409 in git's words.";
|
|
|
|
const paths = {
|
|
"/api/lanes/{id}/git/refs": {
|
|
get: {
|
|
tags: ["Lanes"],
|
|
summary: "Every branch and tag of the lane's repo, for the Branches panel",
|
|
description:
|
|
"`local`: each local branch with its upstream, ahead/behind against it (null without one, `gone` when the upstream ref is gone), last commit subject and date, `merged` (into HEAD — what `branch -d` checks), `worktree` (another worktree holding it), `protected` and `current`. `remotes`: the fetched remote-tracking branches grouped by remote (symbolic HEADs left out). `tags`: name, target commit `sha`, and for an annotated tag its `message`, `tagger` and `date`. At most 1000 of each kind (`truncated`). The repo comes from the lane row (or `?session` / `?source` / `?repo`), never a path from the query; a folder that is not a repo answers available:false with HTTP 200. Read-only, no same-origin guard.",
|
|
operationId: "listLaneGitRefs",
|
|
parameters: [
|
|
{ name: "id", in: "path", required: true, schema: { type: "integer" } },
|
|
{ name: "session", in: "query", required: false, schema: { type: "string" } },
|
|
{ name: "source", in: "query", required: false, schema: { type: "integer" } },
|
|
{ name: "repo", in: "query", required: false, schema: { type: "string" } },
|
|
],
|
|
responses: {
|
|
200: {
|
|
description:
|
|
"{available, current, head, local[], remotes[{remote, branches[]}], tags[], truncated}.",
|
|
},
|
|
404: { description: "Lane not found, ENOSOURCE, ENOREPO or ENOWORKTREE." },
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
/** Add the branch & tag writes to the combined spec's git action entry. */
|
|
function extend(out) {
|
|
const post = out.paths["/api/lanes/{id}/git/{action}"]?.post;
|
|
if (!post) return out;
|
|
const action = (post.parameters || []).find((p) => p.name === "action");
|
|
const list = action?.schema?.enum;
|
|
if (Array.isArray(list)) for (const a of REF_ACTIONS) if (!list.includes(a)) list.push(a);
|
|
if (!post.description.includes("git-refs.js")) post.description += REF_ACTIONS_DOC;
|
|
return out;
|
|
}
|
|
|
|
module.exports = { tags: [], schemas: {}, paths, extend };
|