@volter/twin-github 0.1.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -36
- package/client/github-mirror.css +277 -319
- package/client/github-mirror.d.ts +418 -0
- package/client/github-mirror.js +485 -0
- package/client/github-mirror.tsx +153 -357
- package/client/pulls-rest.ts +159 -0
- package/client/pulls-workspace.tsx +841 -0
- package/dist/client/github-mirror.bundle.js +239 -0
- package/dist/client/github-mirror.css +916 -0
- package/dist/client/github-mirror.d.ts +418 -0
- package/dist/client/github-mirror.js +485 -0
- package/dist/client/github-mirror.tsx +1315 -0
- package/dist/client/pulls-rest.d.ts +42 -0
- package/dist/client/pulls-rest.js +140 -0
- package/dist/client/pulls-rest.ts +159 -0
- package/dist/client/pulls-workspace.bundle.js +22 -0
- package/dist/client/pulls-workspace.d.ts +114 -0
- package/dist/client/pulls-workspace.js +418 -0
- package/dist/client/pulls-workspace.tsx +841 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +40 -0
- package/dist/src/generated/graphql-sdl.gen.json +1 -0
- package/dist/src/generated/graphql.gen.json +1 -0
- package/dist/src/generated/surface.gen.json +1 -0
- package/dist/src/generated/ui.gen.json +1 -0
- package/dist/src/github-budget.d.ts +69 -0
- package/dist/src/github-budget.js +172 -0
- package/dist/src/github-capabilities.d.ts +5 -0
- package/dist/src/github-capabilities.js +4468 -0
- package/dist/src/github-conformance.d.ts +43 -0
- package/dist/src/github-conformance.js +76 -0
- package/dist/src/github-connector.d.ts +307 -0
- package/dist/src/github-connector.js +1398 -0
- package/dist/src/github-events.d.ts +41 -0
- package/dist/src/github-events.js +230 -0
- package/dist/src/github-git-http.d.ts +49 -0
- package/dist/src/github-git-http.js +185 -0
- package/dist/src/github-git-plane.d.ts +114 -0
- package/dist/src/github-git-plane.js +407 -0
- package/dist/src/github-mirror-state.d.ts +2 -0
- package/dist/src/github-mirror-state.js +335 -0
- package/dist/src/github-mirror-ui.d.ts +20 -0
- package/dist/src/github-mirror-ui.js +101 -0
- package/dist/src/github-server.d.ts +14 -0
- package/dist/src/github-server.js +240 -0
- package/dist/src/github-shared.d.ts +12 -0
- package/dist/src/github-shared.js +21 -0
- package/dist/src/github-twin.d.ts +1411 -0
- package/dist/src/github-twin.js +4084 -0
- package/dist/src/github-ui-conformance.d.ts +4 -0
- package/dist/src/github-ui-conformance.js +105 -0
- package/dist/src/github-ui-structure.d.ts +18 -0
- package/dist/src/github-ui-structure.js +251 -0
- package/dist/src/graphql-wire.d.ts +22 -0
- package/dist/src/graphql-wire.js +89 -0
- package/dist/src/index.d.ts +17 -0
- package/dist/src/index.js +101 -0
- package/dist/src/manifest.d.ts +8 -0
- package/dist/src/manifest.js +597 -0
- package/dist/src/npm-registry.d.ts +7 -0
- package/dist/src/npm-registry.js +47 -0
- package/dist/src/screens/app-installation.d.ts +3 -0
- package/dist/src/screens/app-installation.js +166 -0
- package/dist/src/screens/app-manifest.d.ts +3 -0
- package/dist/src/screens/app-manifest.js +81 -0
- package/dist/src/screens/oauth.d.ts +15 -0
- package/dist/src/screens/oauth.js +257 -0
- package/dist/src/screens/session.d.ts +8 -0
- package/dist/src/screens/session.js +159 -0
- package/dist/src/semantics/actions.d.ts +2 -0
- package/dist/src/semantics/actions.js +413 -0
- package/dist/src/semantics/activity.d.ts +4 -0
- package/dist/src/semantics/activity.js +161 -0
- package/dist/src/semantics/apps.d.ts +2 -0
- package/dist/src/semantics/apps.js +144 -0
- package/dist/src/semantics/branches.d.ts +2 -0
- package/dist/src/semantics/branches.js +136 -0
- package/dist/src/semantics/checks.d.ts +2 -0
- package/dist/src/semantics/checks.js +176 -0
- package/dist/src/semantics/code-scanning-upload.d.ts +2 -0
- package/dist/src/semantics/code-scanning-upload.js +97 -0
- package/dist/src/semantics/codespaces.d.ts +2 -0
- package/dist/src/semantics/codespaces.js +58 -0
- package/dist/src/semantics/commits.d.ts +2 -0
- package/dist/src/semantics/commits.js +109 -0
- package/dist/src/semantics/contents.d.ts +2 -0
- package/dist/src/semantics/contents.js +131 -0
- package/dist/src/semantics/deployments.d.ts +2 -0
- package/dist/src/semantics/deployments.js +127 -0
- package/dist/src/semantics/gists.d.ts +2 -0
- package/dist/src/semantics/gists.js +86 -0
- package/dist/src/semantics/git.d.ts +2 -0
- package/dist/src/semantics/git.js +235 -0
- package/dist/src/semantics/graphql.d.ts +7 -0
- package/dist/src/semantics/graphql.js +512 -0
- package/dist/src/semantics/index.d.ts +5 -0
- package/dist/src/semantics/index.js +62 -0
- package/dist/src/semantics/issues.d.ts +2 -0
- package/dist/src/semantics/issues.js +456 -0
- package/dist/src/semantics/keys.d.ts +2 -0
- package/dist/src/semantics/keys.js +66 -0
- package/dist/src/semantics/labels.d.ts +2 -0
- package/dist/src/semantics/labels.js +100 -0
- package/dist/src/semantics/meta.d.ts +12 -0
- package/dist/src/semantics/meta.js +144 -0
- package/dist/src/semantics/notifications.d.ts +4 -0
- package/dist/src/semantics/notifications.js +62 -0
- package/dist/src/semantics/orgs.d.ts +2 -0
- package/dist/src/semantics/orgs.js +420 -0
- package/dist/src/semantics/packages.d.ts +2 -0
- package/dist/src/semantics/packages.js +55 -0
- package/dist/src/semantics/pages.d.ts +2 -0
- package/dist/src/semantics/pages.js +176 -0
- package/dist/src/semantics/projects.d.ts +2 -0
- package/dist/src/semantics/projects.js +239 -0
- package/dist/src/semantics/pulls.d.ts +2 -0
- package/dist/src/semantics/pulls.js +462 -0
- package/dist/src/semantics/push-reactions.d.ts +101 -0
- package/dist/src/semantics/push-reactions.js +513 -0
- package/dist/src/semantics/releases.d.ts +19 -0
- package/dist/src/semantics/releases.js +231 -0
- package/dist/src/semantics/repo-invitations.d.ts +2 -0
- package/dist/src/semantics/repo-invitations.js +98 -0
- package/dist/src/semantics/repos.d.ts +17 -0
- package/dist/src/semantics/repos.js +390 -0
- package/dist/src/semantics/rulesets.d.ts +2 -0
- package/dist/src/semantics/rulesets.js +111 -0
- package/dist/src/semantics/search.d.ts +2 -0
- package/dist/src/semantics/search.js +84 -0
- package/dist/src/semantics/security.d.ts +2 -0
- package/dist/src/semantics/security.js +177 -0
- package/dist/src/semantics/shared.d.ts +61 -0
- package/dist/src/semantics/shared.js +151 -0
- package/dist/src/semantics/users.d.ts +2 -0
- package/dist/src/semantics/users.js +255 -0
- package/dist/src/semantics/webhooks.d.ts +2 -0
- package/dist/src/semantics/webhooks.js +107 -0
- package/dist/test-fixtures/github-a11y-reference.pr-list.SOURCE.md +56 -0
- package/dist/test-fixtures/github-a11y-reference.pr-list.json +1447 -0
- package/dist/test-fixtures/github-comment-schema.SOURCE.md +11 -0
- package/dist/test-fixtures/github-comment-schema.json +702 -0
- package/dist/test-fixtures/github-known-deviations.json +94 -0
- package/dist/test-fixtures/github-openapi-operations.SOURCE.md +76 -0
- package/dist/test-fixtures/github-openapi-operations.json +362 -0
- package/dist/test-fixtures/github-pull-schema.SOURCE.md +37 -0
- package/dist/test-fixtures/github-pull-schema.json +3601 -0
- package/dist/test-fixtures/github-review-schema.SOURCE.md +12 -0
- package/dist/test-fixtures/github-review-schema.json +236 -0
- package/package.json +20 -11
- package/src/cli.ts +7 -6
- package/src/generated/graphql-sdl.gen.json +1 -0
- package/src/generated/graphql.gen.json +1 -0
- package/src/generated/surface.gen.json +1 -0
- package/src/generated/ui.gen.json +1 -0
- package/src/github-a11y-snapshot.uitest.ts +5 -5
- package/src/github-budget.ts +4 -4
- package/src/github-capabilities.ts +1993 -556
- package/src/github-conformance.ts +12 -7
- package/src/github-connector.ts +108 -96
- package/src/github-events.ts +223 -95
- package/src/github-git-http.ts +47 -83
- package/src/github-git-plane.ts +247 -385
- package/src/github-journey.uitest.ts +28 -44
- package/src/github-mirror-state.ts +23 -61
- package/src/github-mirror-ui.ts +16 -10
- package/src/github-server.ts +158 -60
- package/src/github-shared.ts +1 -1
- package/src/github-twin.ts +1525 -4494
- package/src/github-ui-conformance.ts +7 -8
- package/src/github-ui-structure.ts +19 -18
- package/src/graphql-wire.ts +115 -0
- package/src/index.ts +47 -7
- package/src/manifest.ts +605 -0
- package/src/npm-registry.ts +43 -0
- package/src/screens/app-installation.tsx +217 -0
- package/src/screens/app-manifest.tsx +97 -0
- package/src/screens/oauth.tsx +258 -0
- package/src/screens/session.tsx +186 -0
- package/src/semantics/actions.ts +395 -0
- package/src/semantics/activity.ts +171 -0
- package/src/semantics/apps.ts +133 -0
- package/src/semantics/branches.ts +133 -0
- package/src/semantics/checks.ts +163 -0
- package/src/semantics/code-scanning-upload.ts +92 -0
- package/src/semantics/codespaces.ts +58 -0
- package/src/semantics/commits.ts +109 -0
- package/src/semantics/contents.ts +112 -0
- package/src/semantics/deployments.ts +116 -0
- package/src/semantics/gists.ts +85 -0
- package/src/semantics/git.ts +226 -0
- package/src/semantics/graphql.ts +505 -0
- package/src/semantics/index.ts +68 -0
- package/src/semantics/issues.ts +434 -0
- package/src/semantics/keys.ts +66 -0
- package/src/semantics/labels.ts +91 -0
- package/src/semantics/meta.ts +139 -0
- package/src/semantics/notifications.ts +58 -0
- package/src/semantics/orgs.ts +383 -0
- package/src/semantics/packages.ts +59 -0
- package/src/semantics/pages.ts +154 -0
- package/src/semantics/projects.ts +246 -0
- package/src/semantics/pulls.ts +421 -0
- package/src/semantics/push-reactions.ts +471 -0
- package/src/semantics/releases.ts +213 -0
- package/src/semantics/repo-invitations.ts +112 -0
- package/src/semantics/repos.ts +373 -0
- package/src/semantics/rulesets.ts +107 -0
- package/src/semantics/search.ts +83 -0
- package/src/semantics/security.ts +153 -0
- package/src/semantics/shared.ts +181 -0
- package/src/semantics/users.ts +252 -0
- package/src/semantics/webhooks.ts +101 -0
- package/test-fixtures/github-known-deviations.json +7 -8
- package/test-fixtures/github-openapi-operations.json +46 -227
- package/src/github-graphql.ts +0 -398
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// The Git Data API: blobs, trees, commits, annotated tags and refs, backed by the git plane (the
|
|
2
|
+
// engine slot): each create writes a REAL git object into the repository's store and answers its
|
|
3
|
+
// real sha, so a REST-authored chain (blob → tree → commit → ref) is fetchable by an unmodified git
|
|
4
|
+
// client. The kernel records every write as well (the ledger the projection, mirror and webhooks
|
|
5
|
+
// fold). A ref write is a push, as on GitHub: it moves the branch, re-points or closes the open pull
|
|
6
|
+
// requests riding it, and announces push (and pull_request synchronize/closed).
|
|
7
|
+
import { Buffer } from 'node:buffer';
|
|
8
|
+
import type { Semantics, SemanticsContext } from '@volter/world-core';
|
|
9
|
+
import {
|
|
10
|
+
closePrsForDeletedBranch, deleteGitRefInPlane, githubNotFound, isAncestorInPlane, listGitPlaneRefs, objectExistsInPlane, objectTypeInPlane, readGitBlobFromPlane,
|
|
11
|
+
readGitCommitFromPlane, readGitTagFromPlane, readGitTreeFromPlane, syncPrHeadsToBranchTip, toGithubGitBlob, toGithubGitCommit, toGithubGitRef, toGithubGitTagObject,
|
|
12
|
+
toGithubGitTree, updateGitRefInPlane, writeGitBlobToPlane, writeGitCommitToPlane, writeGitTagToPlane, writeGitTreeToPlane,
|
|
13
|
+
type GithubGitRef, type GithubGitTreeEntry, type GithubWriteEvent,
|
|
14
|
+
} from '../github-twin.ts';
|
|
15
|
+
import { announce, atomic, gh, identityOf, put, send } from './shared.ts';
|
|
16
|
+
|
|
17
|
+
/** An owner/name the plane's path validation rejects can never name a twin repository: GitHub's 404,
|
|
18
|
+
* rather than a store error. */
|
|
19
|
+
function unnamed(ctx: SemanticsContext): boolean {
|
|
20
|
+
const { seg } = gh(ctx);
|
|
21
|
+
return !/^[A-Za-z0-9_.-]+$/.test(seg[1]!) || !/^[A-Za-z0-9_.-]+$/.test(seg[2]!) || ['.', '..'].includes(seg[1]!) || ['.', '..'].includes(seg[2]!);
|
|
22
|
+
}
|
|
23
|
+
/** A Git Data write: Not Found for a repository name the plane cannot hold. */
|
|
24
|
+
const gitWrite = (serve: Semantics): Semantics => async (ctx) => (unnamed(ctx) ? send(ctx, githubNotFound()) : serve(ctx));
|
|
25
|
+
|
|
26
|
+
const invalid = (ctx: SemanticsContext, message: string): Response => ctx.reply({ message }, 422);
|
|
27
|
+
const missing = (ctx: SemanticsContext, resource: string, fields: string[]): Response =>
|
|
28
|
+
ctx.reply({ message: 'Validation Failed', errors: fields.map((field) => ({ resource, field, code: 'missing_field' })) }, 422);
|
|
29
|
+
|
|
30
|
+
/** The ref a path names under git/refs/ or git/ref/ (heads/x, tags/x; a ref may hold slashes). */
|
|
31
|
+
const refName = (ctx: SemanticsContext): string => `refs/${decodeURIComponent(gh(ctx).seg.slice(5).join('/'))}`;
|
|
32
|
+
|
|
33
|
+
/** A branch ref's move: the branch row follows, and the open pull requests riding it are re-pointed. */
|
|
34
|
+
async function followBranch(ctx: SemanticsContext, ref: string, sha: string, events: GithubWriteEvent[]): Promise<void> {
|
|
35
|
+
if (ref.startsWith('refs/heads/')) await branchMoved(ctx, ref, sha, events);
|
|
36
|
+
}
|
|
37
|
+
/** A branch ref written through the Git Data API: its branch row and the pull requests riding it follow. */
|
|
38
|
+
async function branchMoved(ctx: SemanticsContext, ref: string, sha: string, events: GithubWriteEvent[]): Promise<void> {
|
|
39
|
+
const { repo, root, occurredAt } = gh(ctx);
|
|
40
|
+
const branch = ref.slice('refs/heads/'.length);
|
|
41
|
+
await put(ctx, { operation: 'branch.create', subjectType: 'branch', subjectId: `branch:${repo}#${branch}`, fields: { repository: repo, name: branch, commit_sha: sha } });
|
|
42
|
+
await syncPrHeadsToBranchTip(repo, branch, sha, root, occurredAt, (pr, base) => events.push({ event: 'pull_request', action: 'synchronize', repository: base, number: Number(pr.number), pr }), atomic(ctx));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function gitSemantics(): Record<string, Semantics> {
|
|
46
|
+
const gitGetRef: Semantics = async (ctx) => {
|
|
47
|
+
const { repo, root, state } = gh(ctx);
|
|
48
|
+
const current = new Map<string, GithubGitRef>(state().gitRefs.filter((r) => r.repository === repo).map((r) => [r.ref, r]));
|
|
49
|
+
for (const r of await listGitPlaneRefs(repo, root)) current.set(r.ref, { ...r, repository: repo });
|
|
50
|
+
const r = current.get(refName(ctx));
|
|
51
|
+
return send(ctx, r ? { status: 200, body: toGithubGitRef(r) } : githubNotFound());
|
|
52
|
+
};
|
|
53
|
+
const gitGetCommit: Semantics = async (ctx) => {
|
|
54
|
+
const { repo, seg, root, state } = gh(ctx);
|
|
55
|
+
const sha = decodeURIComponent(seg[5]!);
|
|
56
|
+
const c = state().gitCommits.find((x) => x.repository === repo && x.sha === sha)
|
|
57
|
+
?? await (async () => { const p = await readGitCommitFromPlane(repo, sha, root); return p ? { repository: repo, ...p } : undefined; })();
|
|
58
|
+
return send(ctx, c ? { status: 200, body: toGithubGitCommit(c) } : githubNotFound());
|
|
59
|
+
};
|
|
60
|
+
const gitGetTree: Semantics = async (ctx) => {
|
|
61
|
+
const { repo, seg, root, state } = gh(ctx);
|
|
62
|
+
const sha = decodeURIComponent(seg[5]!);
|
|
63
|
+
const t = state().gitTrees.find((x) => x.repository === repo && x.sha === sha)
|
|
64
|
+
?? await (async () => { const p = await readGitTreeFromPlane(repo, sha, root); return p ? { repository: repo, sha, tree: p, truncated: false } : undefined; })();
|
|
65
|
+
return send(ctx, t ? { status: 200, body: toGithubGitTree(t) } : githubNotFound());
|
|
66
|
+
};
|
|
67
|
+
const gitGetBlob: Semantics = async (ctx) => {
|
|
68
|
+
const { repo, seg, root, state } = gh(ctx);
|
|
69
|
+
const sha = decodeURIComponent(seg[5]!);
|
|
70
|
+
const b = state().gitBlobs.find((x) => x.repository === repo && x.sha === sha)
|
|
71
|
+
?? await (async () => { const p = await readGitBlobFromPlane(repo, sha, root); return p ? { repository: repo, sha, content_b64: p.content_b64, size: p.size, encoding: 'base64' } : undefined; })();
|
|
72
|
+
return send(ctx, b ? { status: 200, body: toGithubGitBlob(b) } : githubNotFound());
|
|
73
|
+
};
|
|
74
|
+
const gitGetTag: Semantics = async (ctx) => {
|
|
75
|
+
const { repo, seg, root, state } = gh(ctx);
|
|
76
|
+
const sha = decodeURIComponent(seg[5]!);
|
|
77
|
+
const t = state().gitTagObjects.find((x) => x.repository === repo && x.sha === sha)
|
|
78
|
+
?? await (async () => { const p = await readGitTagFromPlane(repo, sha, root); return p ? { repository: repo, ...p } : undefined; })();
|
|
79
|
+
return send(ctx, t ? { status: 200, body: toGithubGitTagObject(t) } : githubNotFound());
|
|
80
|
+
};
|
|
81
|
+
const gitCreateBlob: Semantics = (ctx) => (gitWrite(async (ctx) => {
|
|
82
|
+
const { repo, body, root } = gh(ctx);
|
|
83
|
+
if (body.content === undefined) return missing(ctx, 'Blob', ['content']);
|
|
84
|
+
const encoding = String(body.encoding ?? 'utf-8');
|
|
85
|
+
const content_b64 = encoding === 'base64' ? String(body.content) : Buffer.from(String(body.content), 'utf-8').toString('base64');
|
|
86
|
+
let size = 0; try { size = Buffer.from(content_b64, 'base64').length; } catch { size = content_b64.length; }
|
|
87
|
+
const sha = await writeGitBlobToPlane(repo, content_b64, root);
|
|
88
|
+
await put(ctx, { operation: 'git_blob.create', subjectType: 'git_blob', subjectId: `gitblob:${repo}#${sha}`, fields: { repository: repo, sha, content_b64, size, encoding: 'base64' } });
|
|
89
|
+
return ctx.reply({ sha, url: `https://api.github.com/repos/${repo}/git/blobs/${sha}` }, 201);
|
|
90
|
+
}))(ctx);
|
|
91
|
+
const gitCreateTree: Semantics = (ctx) => (gitWrite(async (ctx) => {
|
|
92
|
+
const { repo, body, root, state } = gh(ctx);
|
|
93
|
+
if (!Array.isArray(body.tree)) return missing(ctx, 'Tree', ['tree']);
|
|
94
|
+
const entries: GithubGitTreeEntry[] = (body.tree as Array<Record<string, unknown>>).map((e) => ({
|
|
95
|
+
path: String(e.path ?? ''), mode: String(e.mode ?? '100644'), type: String(e.type ?? 'blob'), sha: e.sha === null ? '' : String(e.sha ?? ''),
|
|
96
|
+
...(e.size !== undefined ? { size: Number(e.size) } : {}),
|
|
97
|
+
}));
|
|
98
|
+
if (entries.some((e) => e.sha !== '' && !/^[0-9a-f]{40}$/i.test(e.sha))) return invalid(ctx, 'The sha parameter must be exactly 40 characters and contain only [0-9a-f]');
|
|
99
|
+
// a base_tree the store does not hold is a 422: dropping it would lose the base's entries
|
|
100
|
+
if (body.base_tree !== undefined && (!/^[0-9a-f]{40}$/i.test(String(body.base_tree)) || await objectTypeInPlane(repo, String(body.base_tree), root) !== 'tree')) {
|
|
101
|
+
return invalid(ctx, `The base tree parameter (${String(body.base_tree)}) is not a valid tree`);
|
|
102
|
+
}
|
|
103
|
+
const sha = await writeGitTreeToPlane(repo, entries, body.base_tree !== undefined ? String(body.base_tree) : undefined, root);
|
|
104
|
+
// the kernel row records the tree the plane now holds (after the overlay)
|
|
105
|
+
const planeTree = await readGitTreeFromPlane(repo, sha, root) ?? entries.filter((e) => e.sha);
|
|
106
|
+
await put(ctx, { operation: 'git_tree.create', subjectType: 'git_tree', subjectId: `gittree:${repo}#${sha}`, fields: { repository: repo, sha, tree: planeTree, truncated: false } });
|
|
107
|
+
return ctx.reply(toGithubGitTree(state().gitTrees.find((x) => x.repository === repo && x.sha === sha)!), 201);
|
|
108
|
+
}))(ctx);
|
|
109
|
+
const gitCreateCommit: Semantics = (ctx) => (gitWrite(async (ctx) => {
|
|
110
|
+
const { repo, body, occurredAt, root, state } = gh(ctx);
|
|
111
|
+
const absent = (['message', 'tree'] as const).filter((k) => body[k] === undefined);
|
|
112
|
+
if (absent.length) return missing(ctx, 'Commit', absent);
|
|
113
|
+
const parents = Array.isArray(body.parents) ? (body.parents as unknown[]).map(String) : [];
|
|
114
|
+
if (!/^[0-9a-f]{40}$/i.test(String(body.tree)) || parents.some((p) => !/^[0-9a-f]{40}$/i.test(p))) return invalid(ctx, 'The tree and parent parameters must be exactly 40 characters and contain only [0-9a-f]');
|
|
115
|
+
// GitHub defaults the author to the signed-in person and the committer to the author
|
|
116
|
+
const me = identityOf(gh(ctx));
|
|
117
|
+
const author = { name: String(body.author?.name ?? me.name), email: String(body.author?.email ?? me.email) };
|
|
118
|
+
const committer = body.committer?.name !== undefined || body.committer?.email !== undefined
|
|
119
|
+
? { name: String(body.committer?.name ?? author.name), email: String(body.committer?.email ?? author.email) }
|
|
120
|
+
: author;
|
|
121
|
+
const sha = await writeGitCommitToPlane(repo, {
|
|
122
|
+
message: String(body.message), treeSha: String(body.tree), parents,
|
|
123
|
+
author, committer,
|
|
124
|
+
occurredAt: String(body.author?.date ?? body.committer?.date ?? occurredAt),
|
|
125
|
+
}, root);
|
|
126
|
+
// the kernel row records the identity the object's bytes carry
|
|
127
|
+
const fields = { repository: repo, sha, message: String(body.message), tree_sha: String(body.tree), parents, author_name: author.name, author_email: author.email, created_at: occurredAt };
|
|
128
|
+
await put(ctx, { operation: 'git_commit.create', subjectType: 'git_commit', subjectId: `gitcommit:${repo}#${sha}`, fields });
|
|
129
|
+
return ctx.reply(toGithubGitCommit(state().gitCommits.find((x) => x.repository === repo && x.sha === sha)!), 201);
|
|
130
|
+
}))(ctx);
|
|
131
|
+
const gitCreateTag: Semantics = (ctx) => (gitWrite(async (ctx) => {
|
|
132
|
+
const { repo, body, occurredAt, root, state } = gh(ctx);
|
|
133
|
+
const absent = (['tag', 'message', 'object'] as const).filter((k) => body[k] === undefined);
|
|
134
|
+
if (absent.length) return missing(ctx, 'Tag', absent);
|
|
135
|
+
if (!/^[0-9a-f]{40}$/i.test(String(body.object))) return invalid(ctx, 'The object parameter must be exactly 40 characters and contain only [0-9a-f]');
|
|
136
|
+
const me = identityOf(gh(ctx));
|
|
137
|
+
const tagger = { name: String(body.tagger?.name ?? me.name), email: String(body.tagger?.email ?? me.email) };
|
|
138
|
+
const sha = await writeGitTagToPlane(repo, {
|
|
139
|
+
tag: String(body.tag), message: String(body.message), objectSha: String(body.object), objectType: String(body.type ?? 'commit'),
|
|
140
|
+
...(tagger !== undefined ? { tagger } : {}), occurredAt: String(body.tagger?.date ?? occurredAt),
|
|
141
|
+
}, root);
|
|
142
|
+
const fields = { repository: repo, sha, tag: String(body.tag), message: String(body.message), object_sha: String(body.object), object_type: String(body.type ?? 'commit'), tagger_name: body.tagger?.name, created_at: occurredAt };
|
|
143
|
+
await put(ctx, { operation: 'git_tag_object.create', subjectType: 'git_tag_object', subjectId: `gittag:${repo}#${sha}`, fields });
|
|
144
|
+
return ctx.reply(toGithubGitTagObject(state().gitTagObjects.find((x) => x.repository === repo && x.sha === sha)!), 201);
|
|
145
|
+
}))(ctx);
|
|
146
|
+
const gitUpdateRef: Semantics = (ctx) => (gitWrite(async (ctx) => {
|
|
147
|
+
const { repo, body, root, state } = gh(ctx);
|
|
148
|
+
const ref = refName(ctx);
|
|
149
|
+
const existing = state().gitRefs.find((x) => x.repository === repo && x.ref === ref);
|
|
150
|
+
if (!existing) return send(ctx, githubNotFound());
|
|
151
|
+
if (body.sha === undefined) return missing(ctx, 'Reference', ['sha']);
|
|
152
|
+
const force = body.force === true || body.force === 'true';
|
|
153
|
+
if (!force
|
|
154
|
+
&& await objectExistsInPlane(repo, existing.object_sha, root) && await objectExistsInPlane(repo, String(body.sha), root)
|
|
155
|
+
&& !await isAncestorInPlane(repo, existing.object_sha, String(body.sha), root)) {
|
|
156
|
+
return invalid(ctx, 'Update is not a fast forward');
|
|
157
|
+
}
|
|
158
|
+
await put(ctx, { operation: 'git_ref.update', subjectType: 'git_ref', subjectId: `gitref:${repo}#${ref}`, fields: { repository: repo, ref, object_sha: String(body.sha), object_type: existing.object_type } });
|
|
159
|
+
await updateGitRefInPlane(repo, ref, String(body.sha), root);
|
|
160
|
+
const events: GithubWriteEvent[] = [{ event: 'push', action: '', repository: repo, number: 0, ref, before: existing.object_sha, after: String(body.sha) }];
|
|
161
|
+
await followBranch(ctx, ref, String(body.sha), events);
|
|
162
|
+
const answer = toGithubGitRef(state().gitRefs.find((x) => x.repository === repo && x.ref === ref)!);
|
|
163
|
+
await announce(ctx, events);
|
|
164
|
+
return ctx.reply(answer);
|
|
165
|
+
}))(ctx);
|
|
166
|
+
const gitDeleteRef: Semantics = (ctx) => (gitWrite(async (ctx) => {
|
|
167
|
+
const { repo, root, occurredAt, state } = gh(ctx);
|
|
168
|
+
const ref = refName(ctx);
|
|
169
|
+
const existing = state().gitRefs.find((x) => x.repository === repo && x.ref === ref);
|
|
170
|
+
if (!existing) return send(ctx, githubNotFound());
|
|
171
|
+
await put(ctx, { operation: 'git_ref.delete', subjectType: 'git_ref', subjectId: `gitref:${repo}#${ref}`, fields: { deleted: true, repository: repo, ref } });
|
|
172
|
+
await deleteGitRefInPlane(repo, ref, root);
|
|
173
|
+
const events: GithubWriteEvent[] = [{ event: 'push', action: '', repository: repo, number: 0, ref, before: existing.object_sha, after: '0'.repeat(40) }];
|
|
174
|
+
if (ref.startsWith('refs/heads/')) {
|
|
175
|
+
const branch = ref.slice('refs/heads/'.length);
|
|
176
|
+
if (state().branches.some((b) => b.repository === repo && b.name === branch)) {
|
|
177
|
+
await put(ctx, { operation: 'branch.delete', subjectType: 'branch', subjectId: `branch:${repo}#${branch}`, fields: { deleted: true, repository: repo, name: branch } });
|
|
178
|
+
}
|
|
179
|
+
await closePrsForDeletedBranch(repo, branch, root, occurredAt, (pr, base) => events.push({ event: 'pull_request', action: 'closed', repository: base, number: Number(pr.number), pr }), atomic(ctx), { operation: 'git/delete-ref', actor: 'api' });
|
|
180
|
+
}
|
|
181
|
+
await announce(ctx, events);
|
|
182
|
+
return send(ctx, { status: 204, body: undefined });
|
|
183
|
+
}))(ctx);
|
|
184
|
+
return {
|
|
185
|
+
// exactly one ref (heads/x or tags/x), the kernel's refs overlaid by the plane's
|
|
186
|
+
'git/get-ref': gitGetRef,
|
|
187
|
+
// Object reads consult the kernel row first (REST-authored objects land in both with the same real
|
|
188
|
+
// sha), then the plane, so an object that arrived by `git push` is visible here too.
|
|
189
|
+
'git/get-commit': gitGetCommit,
|
|
190
|
+
'git/get-tree': gitGetTree,
|
|
191
|
+
'git/get-blob': gitGetBlob,
|
|
192
|
+
'git/get-tag': gitGetTag,
|
|
193
|
+
// content in base64 or utf-8; answers {sha, url}
|
|
194
|
+
'git/create-blob': gitCreateBlob,
|
|
195
|
+
// {tree:[{path,mode,type,sha}]} with base_tree overlay semantics (sha:null deletes a path), as GitHub
|
|
196
|
+
'git/create-tree': gitCreateTree,
|
|
197
|
+
// {message, tree, parents}; an author or committer date pins the sha as GitHub honors it
|
|
198
|
+
'git/create-commit': gitCreateCommit,
|
|
199
|
+
// an ANNOTATED tag object {tag, message, object, type}
|
|
200
|
+
'git/create-tag': gitCreateTag,
|
|
201
|
+
// {ref:'refs/heads/x', sha}; 422 when either is missing or the ref exists. A ref minted against a
|
|
202
|
+
// sha the plane has never seen stays control-plane-only.
|
|
203
|
+
'git/create-ref': gitWrite(async (ctx) => {
|
|
204
|
+
const { repo, body, root, state } = gh(ctx);
|
|
205
|
+
const absent = (['ref', 'sha'] as const).filter((k) => body[k] === undefined);
|
|
206
|
+
if (absent.length) return missing(ctx, 'Reference', absent);
|
|
207
|
+
const ref = String(body.ref);
|
|
208
|
+
if (!ref.startsWith('refs/')) return ctx.reply({ message: 'Validation Failed', errors: [{ resource: 'Reference', field: 'ref', code: 'invalid' }] }, 422);
|
|
209
|
+
if (state().gitRefs.some((x) => x.repository === repo && x.ref === ref)) return invalid(ctx, 'Reference already exists');
|
|
210
|
+
const planeType = await objectTypeInPlane(repo, String(body.sha), root);
|
|
211
|
+
const object_type = planeType === 'tag' || (ref.startsWith('refs/tags/') && state().gitTagObjects.some((t) => t.repository === repo && t.sha === String(body.sha))) ? 'tag' : 'commit';
|
|
212
|
+
await put(ctx, { operation: 'git_ref.create', subjectType: 'git_ref', subjectId: `gitref:${repo}#${ref}`, fields: { repository: repo, ref, object_sha: String(body.sha), object_type } });
|
|
213
|
+
await updateGitRefInPlane(repo, ref, String(body.sha), root);
|
|
214
|
+
const events: GithubWriteEvent[] = [{ event: 'push', action: '', repository: repo, number: 0, ref, before: '0'.repeat(40), after: String(body.sha) }];
|
|
215
|
+
await followBranch(ctx, ref, String(body.sha), events);
|
|
216
|
+
const answer = toGithubGitRef(state().gitRefs.find((x) => x.repository === repo && x.ref === ref)!);
|
|
217
|
+
await announce(ctx, events);
|
|
218
|
+
return ctx.reply(answer, 201);
|
|
219
|
+
}),
|
|
220
|
+
// Fast-forward, or force-update, a ref. A non-fast-forward needs force (true or "true", as gh's -F
|
|
221
|
+
// and -f send it); decidable only when both commits are in the plane.
|
|
222
|
+
'git/update-ref': gitUpdateRef,
|
|
223
|
+
// deleting a branch ref removes the branch and closes the open pull requests riding it
|
|
224
|
+
'git/delete-ref': gitDeleteRef,
|
|
225
|
+
};
|
|
226
|
+
}
|