2b5d7268ed
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.
119 lines
5.9 KiB
JavaScript
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 };
|