Files
AgentHub/server/openapi-extra/git-rewrite.js
T
nntrivi2001 2b5d7268ed feat(git): 3-way merge editor, reflog, rebase/interactive rebase, reset with undo, force-with-lease push, remote branches and remotes
Server: git-rewrite.js (rebase + continue/skip/abort, rebase-interactive with
no editor, reset soft/mixed/keep/hard, undo-rewrite, force-push with lease,
remote-branch-delete, remote add/rename/remove) and git-conflicts.js
(conflict stages read + conflict-save, reflog), dispatched from runGitAction;
operationInProgress now reports a rebase and only ALLOWED_WHILE_REBASING
writes run during one. New reads: /git/conflict, /git/reflog, /git/remotes,
/git/rewrite-plan. Client: History tools menu, MergeEditor, ReflogView,
RebaseDialog, InteractiveRebase, ResetDialog, ForcePushDialog, RemotesDialog,
rebase banner Skip. CLAUDE.md git-writes rule rewritten for the new actions.
2026-11-02 12:12:45 +07:00

119 lines
5.9 KiB
JavaScript

/**
* @file OpenAPI fragment for the better-conflicts and history-rewrite READS
* of a workspace (server/lib/git-conflicts.js, server/lib/git-rewrite.js): one
* conflicted file's three stages for the 3-way merge editor, the reflog, the
* repo's remotes and the plan a rewrite dialog shows before it asks. The
* writes themselves are actions of POST /api/lanes/{id}/git/{action}
* (documented in server/openapi-extra/lanes.js). Exports `{ tags, schemas,
* paths }` for server/openapi-extra.js.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
const idParam = { name: "id", in: "path", required: true, schema: { type: "integer" } };
const target = [
{ name: "session", in: "query", schema: { type: "string" } },
{ name: "source", in: "query", schema: { type: "integer" } },
{ name: "repo", in: "query", schema: { type: "string" } },
];
const paths = {
"/api/lanes/{id}/git/conflict": {
get: {
tags: ["Lanes"],
summary: "One conflicted file's three sides, for the merge editor",
description:
"Only a path `git ls-files -u` lists as unmerged right now (400 EBADPATH otherwise), with the read path's guards (lexical lane guard, 403 ESECRET, 1 MB cap). Each stage is read by its blob id: base (:1), ours / current (:2), theirs / incoming (:3); a side git does not have (added on one side, deleted on the other) is null. `working` is the file on disk with its markers and `sha` its content sha — the `baseSha` the `conflict-save` action must name. `operation` is merge, cherry-pick, revert or rebase (in a rebase, ours is the branch being rebased ONTO and theirs the commit being replayed); `incoming` is that operation's commit. Read-only, no same-origin guard.",
operationId: "readLaneConflict",
parameters: [
idParam,
{ name: "path", in: "query", required: true, schema: { type: "string" } },
...target,
],
responses: {
200: {
description:
"{available, path, operation, branch, incoming:{sha,short,subject}|null, base, ours, theirs, working, sha, usable, binary, tooBig}.",
},
400: { description: "EBADPATH." },
403: { description: "ESECRET or EFOREIGN." },
404: { description: "Lane not found." },
},
},
},
"/api/lanes/{id}/git/reflog": {
get: {
tags: ["Lanes"],
summary: "A page of the reflog (read-only)",
description:
"`git log -g` of HEAD (default) or of a local branch that exists (looked up, 400 EBADBRANCH / ENOBRANCH otherwise), newest first, `limit` (default 50, max 200) entries from `skip`. A ref with no reflog is an empty list; a folder that is not a repo answers {available:false}. Read-only, no same-origin guard.",
operationId: "readLaneReflog",
parameters: [
idParam,
{ name: "ref", in: "query", schema: { type: "string", default: "HEAD" } },
{ name: "skip", in: "query", schema: { type: "integer", default: 0 } },
{ name: "limit", in: "query", schema: { type: "integer", default: 50 } },
...target,
],
responses: {
200: {
description:
"{available, ref, more, entries:[{index, selector, sha, short, action, message, time, subject}]} — action/message split git's reflog subject (`commit: fix`), time is unix seconds.",
},
400: { description: "EBADBRANCH or ENOBRANCH." },
404: { description: "Lane not found." },
},
},
},
"/api/lanes/{id}/git/remotes": {
get: {
tags: ["Lanes"],
summary: "The repo's remotes",
description:
"Each remote's fetch and push URL and its default branch (`refs/remotes/<name>/HEAD`). An http(s) URL loses its whole userinfo and any other keeps only the user name, so no password or token is ever answered. Read-only, no same-origin guard.",
operationId: "listLaneRemotes",
parameters: [idParam, ...target],
responses: {
200: {
description:
"{available, remotes:[{name, url, pushUrl, defaultBranch, branches}]} — branches are the fetched remote-tracking branches (every one, a local copy or not).",
},
404: { description: "Lane not found." },
},
},
},
"/api/lanes/{id}/git/rewrite-plan": {
get: {
tags: ["Lanes"],
summary: "What a history rewrite would do, before it asks",
description:
"kind=rebase&onto=<branch> — the commits replayed (oldest first, each with pushed), how far behind the branch is; kind=interactive[&base=<sha>] — the commits an interactive rebase may rewrite (default: those no remote-tracking branch holds), base and whether a merge is in the range; kind=reset&target=<sha> — related (ancestor or descendant), how many commits are dropped/added and how many dropped ones are pushed; kind=push — where a force push goes (branch, remote, remoteBranch), the lease sha (remoteSha, from the last fetch), ahead/behind, protected and allowed. Every answer carries head (the full sha a write must name, else ESTALE), branch, subdir, operation and dirty. Read-only, no same-origin guard.",
operationId: "planLaneRewrite",
parameters: [
idParam,
{
name: "kind",
in: "query",
required: true,
schema: { type: "string", enum: ["rebase", "interactive", "reset", "push"] },
},
{ name: "onto", in: "query", schema: { type: "string" } },
{ name: "base", in: "query", schema: { type: "string" } },
{ name: "target", in: "query", schema: { type: "string" } },
...target,
],
responses: {
200: {
description: "{available, kind, head, branch, subdir, operation, dirty, …kind's facts}.",
},
400: {
description:
"EBADMODE, EBADBRANCH, ENOBRANCH, EBADSHA, ENOTRELATED, ENOPARENT, EDETACHED or ENOREMOTE.",
},
404: { description: "Lane not found." },
},
},
},
};
module.exports = { tags: [], schemas: {}, paths };