@dotobokuri/fleet-console 1.67.0 → 1.69.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/dist/cli.mjs +470 -49
- package/dist/client/assets/{_baseUniq-DG32CEpb.js → _baseUniq-BPBd-J9P.js} +1 -1
- package/dist/client/assets/{arc-CoHkSCMF.js → arc-spHqy4X3.js} +1 -1
- package/dist/client/assets/{architectureDiagram-Q4EWVU46-f0RKHqJg.js → architectureDiagram-Q4EWVU46-7CHqKfxu.js} +1 -1
- package/dist/client/assets/{blockDiagram-DXYQGD6D-Dxb3hLAV.js → blockDiagram-DXYQGD6D-YasFDgl_.js} +1 -1
- package/dist/client/assets/{c4Diagram-AHTNJAMY-C5q_ahSd.js → c4Diagram-AHTNJAMY-CcoPJq56.js} +1 -1
- package/dist/client/assets/channel-DfEjadrw.js +1 -0
- package/dist/client/assets/{chunk-4BX2VUAB-BS0W4Fpt.js → chunk-4BX2VUAB-MYBJn9BE.js} +1 -1
- package/dist/client/assets/{chunk-4TB4RGXK-p03eWKTO.js → chunk-4TB4RGXK-C1wg6GR_.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-BB1ALVX2.js → chunk-55IACEB6-BoufMT1O.js} +1 -1
- package/dist/client/assets/{chunk-EDXVE4YY-DV4N-Npm.js → chunk-EDXVE4YY-BzAhAIl3.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-Da8VspnD.js → chunk-FMBD7UC4-yVrLsKzP.js} +1 -1
- package/dist/client/assets/{chunk-OYMX7WX6-CWTmAQhy.js → chunk-OYMX7WX6-YiiKaqRQ.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-DE9xUsy3.js → chunk-QZHKN3VN-CqWQlGHT.js} +1 -1
- package/dist/client/assets/{chunk-YZCP3GAM-BxriNNdB.js → chunk-YZCP3GAM-9yLOmU5k.js} +1 -1
- package/dist/client/assets/classDiagram-6PBFFD2Q-D9vQdv0q.js +1 -0
- package/dist/client/assets/classDiagram-v2-HSJHXN6E-D9vQdv0q.js +1 -0
- package/dist/client/assets/clone-Ck2x5JX2.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-B4sjskIn.js → cose-bilkent-S5V4N54A-XkTuSq7v.js} +1 -1
- package/dist/client/assets/{dagre-KV5264BT-oJms5aS3.js → dagre-KV5264BT-DyzXIEAe.js} +1 -1
- package/dist/client/assets/{diagram-5BDNPKRD-BAbLGddM.js → diagram-5BDNPKRD-DF24YlCa.js} +1 -1
- package/dist/client/assets/{diagram-G4DWMVQ6-BrhRQwe-.js → diagram-G4DWMVQ6-CVhcj9RK.js} +1 -1
- package/dist/client/assets/{diagram-MMDJMWI5-Buxu7Ng3.js → diagram-MMDJMWI5-C-kGKk4K.js} +1 -1
- package/dist/client/assets/{diagram-TYMM5635-Cnp__7LJ.js → diagram-TYMM5635-mr285WbR.js} +1 -1
- package/dist/client/assets/{erDiagram-SMLLAGMA-ctmZHrVO.js → erDiagram-SMLLAGMA-BtEx-XlU.js} +1 -1
- package/dist/client/assets/{flowDiagram-DWJPFMVM-DsQXJqF_.js → flowDiagram-DWJPFMVM-DuWy3xzN.js} +1 -1
- package/dist/client/assets/{ganttDiagram-T4ZO3ILL-SAFJNY9M.js → ganttDiagram-T4ZO3ILL-B2-xEikU.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-UUTBAWPF-Bd5Dq0vQ.js → gitGraphDiagram-UUTBAWPF-EM17u_t7.js} +1 -1
- package/dist/client/assets/{graph-DzMOPhFp.js → graph-xuE12FIq.js} +1 -1
- package/dist/client/assets/index-C5EOZGlC.js +466 -0
- package/dist/client/assets/index-CHHE3nPw.css +1 -0
- package/dist/client/assets/{infoDiagram-42DDH7IO-BrEfG9P4.js → infoDiagram-42DDH7IO-B3MF4XtW.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-UXIWVN3A-Dg22orvH.js → ishikawaDiagram-UXIWVN3A-DoQ2mGyh.js} +1 -1
- package/dist/client/assets/{journeyDiagram-VCZTEJTY-DEcjEc6c.js → journeyDiagram-VCZTEJTY-GeR9sFH-.js} +1 -1
- package/dist/client/assets/{kanban-definition-6JOO6SKY-C2Xa4znN.js → kanban-definition-6JOO6SKY-DrrYMuvL.js} +1 -1
- package/dist/client/assets/{layout-B7E_-0ev.js → layout-DH63A-3u.js} +1 -1
- package/dist/client/assets/{linear-BXJsh3FL.js → linear-BIjE6zB1.js} +1 -1
- package/dist/client/assets/{mermaid.core-CdZpLnyY.js → mermaid.core-yrrC2Lqo.js} +4 -4
- package/dist/client/assets/{min-CEAFFcJY.js → min-CR-H8E6U.js} +1 -1
- package/dist/client/assets/{mindmap-definition-QFDTVHPH-iXzcnu1C.js → mindmap-definition-QFDTVHPH-BzSC43J-.js} +1 -1
- package/dist/client/assets/{pieDiagram-DEJITSTG-CdRN75ad.js → pieDiagram-DEJITSTG-BdghNtCc.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-34T5L4WZ-Dscl0EFH.js → quadrantDiagram-34T5L4WZ-s_zN2epw.js} +1 -1
- package/dist/client/assets/{requirementDiagram-MS252O5E-CLmSCaQs.js → requirementDiagram-MS252O5E-DuiGG6Tx.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-XADWPNL6-BfPk87jf.js → sankeyDiagram-XADWPNL6-Bp0JBk2g.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-FGHM5R23-DE8sU8ej.js → sequenceDiagram-FGHM5R23-AzQ3M31V.js} +1 -1
- package/dist/client/assets/{stateDiagram-FHFEXIEX-BmaQc1Nm.js → stateDiagram-FHFEXIEX-BGgm44ev.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-CZDbnuN-.js +1 -0
- package/dist/client/assets/{timeline-definition-GMOUNBTQ-C4Z0umzx.js → timeline-definition-GMOUNBTQ-UqfsiBJY.js} +1 -1
- package/dist/client/assets/{vennDiagram-DHZGUBPP-BKlcRXlE.js → vennDiagram-DHZGUBPP-PsCGF9nR.js} +1 -1
- package/dist/client/assets/{wardley-RL74JXVD-B0RT7bAW.js → wardley-RL74JXVD-BDQbnh-d.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-NUSXRM2D-mx28kvam.js → wardleyDiagram-NUSXRM2D-CtVA1JMs.js} +1 -1
- package/dist/client/assets/{xychartDiagram-5P7HB3ND-DU6qIPI0.js → xychartDiagram-5P7HB3ND-BsLlZWbc.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/fleet-plugins/file-explorer/routes.mjs +21 -2
- package/dist/fleet-plugins/ledger/routes.mjs +100 -3
- package/dist/fleet-plugins/quota/routes.mjs +100 -3
- package/dist/fleet-plugins/repository/routes.mjs +910 -26
- package/dist/fleet-plugins/scuttlebutt/routes.mjs +102 -4
- package/dist/fleet-plugins/terminal/routes.mjs +717 -935
- package/dist/fleet.mjs +923 -807
- package/package.json +1 -1
- package/dist/client/assets/channel-C-Qx-Oi-.js +0 -1
- package/dist/client/assets/classDiagram-6PBFFD2Q-DA7e8Mft.js +0 -1
- package/dist/client/assets/classDiagram-v2-HSJHXN6E-DA7e8Mft.js +0 -1
- package/dist/client/assets/clone-Rpb38n4i.js +0 -1
- package/dist/client/assets/index-BLA_qnPk.css +0 -1
- package/dist/client/assets/index-CGE8u6Es.js +0 -465
- package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-TcoFpvuT.js +0 -1
|
@@ -7,16 +7,16 @@ import path3 from 'node:path';
|
|
|
7
7
|
function definePlugin(definition) {
|
|
8
8
|
return definition;
|
|
9
9
|
}
|
|
10
|
-
function registerRouter(ctx,
|
|
10
|
+
function registerRouter(ctx, path12, handler, catalog) {
|
|
11
11
|
if (catalog) {
|
|
12
|
-
ctx.registerRouter(normalizePath(
|
|
12
|
+
ctx.registerRouter(normalizePath(path12), handler, catalog);
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
|
-
ctx.registerRouter(normalizePath(
|
|
15
|
+
ctx.registerRouter(normalizePath(path12), handler);
|
|
16
16
|
}
|
|
17
|
-
function normalizePath(
|
|
18
|
-
if (
|
|
19
|
-
return
|
|
17
|
+
function normalizePath(path12) {
|
|
18
|
+
if (path12.length === 0 || path12 === "/") return "/";
|
|
19
|
+
return path12;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// ../../packages/core-process/src/bin-resolver.ts
|
|
@@ -418,7 +418,7 @@ async function handleRepositoryFile(req, res, ctx) {
|
|
|
418
418
|
return;
|
|
419
419
|
}
|
|
420
420
|
const mode = body.mode;
|
|
421
|
-
if (mode !== "unified" && mode !== "untracked") {
|
|
421
|
+
if (mode !== "unified" && mode !== "untracked" && mode !== "staged" && mode !== "worktree") {
|
|
422
422
|
ctx.host.http.writeJson(res, 400, { error: "invalid_mode" });
|
|
423
423
|
return;
|
|
424
424
|
}
|
|
@@ -490,11 +490,17 @@ async function handleRepositoryFile(req, res, ctx) {
|
|
|
490
490
|
} catch {
|
|
491
491
|
}
|
|
492
492
|
let result;
|
|
493
|
-
|
|
494
|
-
result = await runGit(["diff", "--no-ext-diff", "--no-textconv", "
|
|
495
|
-
}
|
|
496
|
-
if (!isNoHeadError(err)) throw err;
|
|
493
|
+
if (mode === "worktree") {
|
|
494
|
+
result = await runGit(["diff", "--no-ext-diff", "--no-textconv", "--relative", "--unified=3", "--", literalPathspec(relativePath)], { cwd: gitCwd });
|
|
495
|
+
} else if (mode === "staged") {
|
|
497
496
|
result = await runGit(["diff", "--no-ext-diff", "--no-textconv", "--cached", "--relative", "--unified=3", "--", literalPathspec(relativePath)], { cwd: gitCwd });
|
|
497
|
+
} else {
|
|
498
|
+
try {
|
|
499
|
+
result = await runGit(["diff", "--no-ext-diff", "--no-textconv", "HEAD", "--relative", "--unified=3", "--", literalPathspec(relativePath)], { cwd: gitCwd });
|
|
500
|
+
} catch (err) {
|
|
501
|
+
if (!isNoHeadError(err)) throw err;
|
|
502
|
+
result = await runGit(["diff", "--no-ext-diff", "--no-textconv", "--cached", "--relative", "--unified=3", "--", literalPathspec(relativePath)], { cwd: gitCwd });
|
|
503
|
+
}
|
|
498
504
|
}
|
|
499
505
|
ctx.host.http.writeJson(res, 200, { content: result.stdout, truncated: result.truncated });
|
|
500
506
|
} catch (error) {
|
|
@@ -602,6 +608,254 @@ async function handleRepositoryCommit(req, res, ctx) {
|
|
|
602
608
|
function isPlainObject3(value) {
|
|
603
609
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
604
610
|
}
|
|
611
|
+
var MAX_PATHS_PER_REQUEST = 1e3;
|
|
612
|
+
function readStagePaths(value) {
|
|
613
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > MAX_PATHS_PER_REQUEST) return null;
|
|
614
|
+
const paths = [];
|
|
615
|
+
for (const raw of value) {
|
|
616
|
+
if (typeof raw !== "string" || raw === "" || raw.includes("\0")) return null;
|
|
617
|
+
if (path3.isAbsolute(raw) || path3.posix.isAbsolute(raw)) return null;
|
|
618
|
+
const normalized = path3.posix.normalize(raw);
|
|
619
|
+
if (normalized === ".." || normalized.startsWith("../")) return null;
|
|
620
|
+
paths.push(raw);
|
|
621
|
+
}
|
|
622
|
+
return paths;
|
|
623
|
+
}
|
|
624
|
+
function literalPathspecs(paths) {
|
|
625
|
+
return paths.map((entry) => `:(literal)${entry}`);
|
|
626
|
+
}
|
|
627
|
+
function classifyWriteError(stderr) {
|
|
628
|
+
return /index\.lock|Unable to create .* File exists/i.test(stderr) ? "index_locked" : null;
|
|
629
|
+
}
|
|
630
|
+
async function resolveWriteContext(req, res, ctx) {
|
|
631
|
+
if (req.method !== "POST") {
|
|
632
|
+
ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
if (!ctx.host.security.isTerminalAuthorized(req)) {
|
|
636
|
+
ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
const body = await ctx.host.http.readJsonBody(req);
|
|
640
|
+
if (!isPlainObject3(body) || "subPath" in body || typeof body.theaterId !== "string") {
|
|
641
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
644
|
+
const theaterPath = ctx.host.paths.resolveTheaterPath(body.theaterId);
|
|
645
|
+
if (!theaterPath) {
|
|
646
|
+
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
647
|
+
return null;
|
|
648
|
+
}
|
|
649
|
+
try {
|
|
650
|
+
const { gitCwd } = await resolveGitCwd(theaterPath, body.repoRel);
|
|
651
|
+
return { gitCwd, body };
|
|
652
|
+
} catch (error) {
|
|
653
|
+
if (error instanceof InvalidRepoError) {
|
|
654
|
+
ctx.host.http.writeJson(res, 400, { error: error.code });
|
|
655
|
+
return null;
|
|
656
|
+
}
|
|
657
|
+
throw error;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
function writeGitFailure(res, ctx, error) {
|
|
661
|
+
if (error instanceof GitExecutorError) {
|
|
662
|
+
if (error.code === "no_git_repo" || error.code === "git_unavailable") {
|
|
663
|
+
ctx.host.http.writeJson(res, 422, { error: error.code });
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
if (error.code === "timeout") {
|
|
667
|
+
ctx.host.http.writeJson(res, 422, { error: "timeout" });
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
const locked = classifyWriteError(error.stderr);
|
|
671
|
+
if (locked) {
|
|
672
|
+
ctx.host.http.writeJson(res, 409, { error: locked });
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
ctx.host.http.writeJson(res, 500, { error: "git_failed" });
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
throw error;
|
|
679
|
+
}
|
|
680
|
+
async function handleRepositoryStage(req, res, ctx) {
|
|
681
|
+
const resolved = await resolveWriteContext(req, res, ctx);
|
|
682
|
+
if (!resolved) return;
|
|
683
|
+
if (resolved.body.all === true) {
|
|
684
|
+
try {
|
|
685
|
+
await runGit(["add", "-A", "--", "."], { cwd: resolved.gitCwd });
|
|
686
|
+
ctx.host.http.writeJson(res, 200, { ok: true });
|
|
687
|
+
} catch (error) {
|
|
688
|
+
writeGitFailure(res, ctx, error);
|
|
689
|
+
}
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
const paths = readStagePaths(resolved.body.paths);
|
|
693
|
+
if (!paths) {
|
|
694
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_paths" });
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
try {
|
|
698
|
+
await runGit(["add", "--", ...literalPathspecs(paths)], { cwd: resolved.gitCwd });
|
|
699
|
+
ctx.host.http.writeJson(res, 200, { ok: true });
|
|
700
|
+
} catch (error) {
|
|
701
|
+
writeGitFailure(res, ctx, error);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
async function handleRepositoryUnstage(req, res, ctx) {
|
|
705
|
+
const resolved = await resolveWriteContext(req, res, ctx);
|
|
706
|
+
if (!resolved) return;
|
|
707
|
+
const all = resolved.body.all === true;
|
|
708
|
+
const paths = all ? [] : readStagePaths(resolved.body.paths);
|
|
709
|
+
if (!paths) {
|
|
710
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_paths" });
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
const pathspecs = all ? ["."] : literalPathspecs(paths);
|
|
714
|
+
try {
|
|
715
|
+
try {
|
|
716
|
+
await runGit(["restore", "--staged", "--", ...pathspecs], { cwd: resolved.gitCwd });
|
|
717
|
+
} catch (error) {
|
|
718
|
+
if (!(error instanceof GitExecutorError) || !/could not resolve HEAD|unknown revision|bad revision/i.test(error.stderr)) throw error;
|
|
719
|
+
await runGit(["rm", "-r", "--cached", "-q", "--", ...pathspecs], { cwd: resolved.gitCwd });
|
|
720
|
+
}
|
|
721
|
+
ctx.host.http.writeJson(res, 200, { ok: true });
|
|
722
|
+
} catch (error) {
|
|
723
|
+
writeGitFailure(res, ctx, error);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
async function handleRepositoryDiscard(req, res, ctx) {
|
|
727
|
+
const resolved = await resolveWriteContext(req, res, ctx);
|
|
728
|
+
if (!resolved) return;
|
|
729
|
+
const hasTracked = resolved.body.paths !== void 0;
|
|
730
|
+
const hasUntracked = resolved.body.untrackedPaths !== void 0;
|
|
731
|
+
if (!hasTracked && !hasUntracked) {
|
|
732
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_paths" });
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
const tracked = hasTracked ? readStagePaths(resolved.body.paths) : [];
|
|
736
|
+
const untracked = hasUntracked ? readStagePaths(resolved.body.untrackedPaths) : [];
|
|
737
|
+
if (tracked === null || untracked === null) {
|
|
738
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_paths" });
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
try {
|
|
742
|
+
if (tracked.length > 0) {
|
|
743
|
+
await runGit(["restore", "--worktree", "--", ...literalPathspecs(tracked)], { cwd: resolved.gitCwd });
|
|
744
|
+
}
|
|
745
|
+
if (untracked.length > 0) {
|
|
746
|
+
await runGit(["clean", "-f", "-d", "--", ...literalPathspecs(untracked)], { cwd: resolved.gitCwd });
|
|
747
|
+
}
|
|
748
|
+
ctx.host.http.writeJson(res, 200, { ok: true });
|
|
749
|
+
} catch (error) {
|
|
750
|
+
writeGitFailure(res, ctx, error);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// ../fleet-plugins/repository/server/commit-create.ts
|
|
755
|
+
function isPlainObject4(value) {
|
|
756
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
757
|
+
}
|
|
758
|
+
var MAX_SUBJECT_LENGTH = 500;
|
|
759
|
+
var MAX_BODY_LENGTH = 1e5;
|
|
760
|
+
var COMMIT_HARDENING_ARGS = [
|
|
761
|
+
"-c",
|
|
762
|
+
`core.hooksPath=${process.platform === "win32" ? "NUL" : "/dev/null"}`,
|
|
763
|
+
"-c",
|
|
764
|
+
"commit.gpgSign=false"
|
|
765
|
+
];
|
|
766
|
+
async function hasStagedChanges(gitCwd) {
|
|
767
|
+
try {
|
|
768
|
+
return (await runGit(["diff", "--cached", "--name-only", "--", "."], { cwd: gitCwd })).stdout.trim() !== "";
|
|
769
|
+
} catch (error) {
|
|
770
|
+
if (error instanceof GitExecutorError && /unknown revision|bad revision|ambiguous argument/i.test(error.stderr)) {
|
|
771
|
+
return (await runGit(["ls-files", "--cached"], { cwd: gitCwd })).stdout.trim() !== "";
|
|
772
|
+
}
|
|
773
|
+
throw error;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
function classifyCommitError(stderr) {
|
|
777
|
+
if (/Please tell me who you are|unable to auto-detect email address|empty ident name/i.test(stderr)) return "identity_missing";
|
|
778
|
+
if (/nothing to commit|no changes added to commit|nothing added to commit/i.test(stderr)) return "nothing_to_commit";
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
781
|
+
async function handleRepositoryCommitCreate(req, res, ctx) {
|
|
782
|
+
if (req.method !== "POST") {
|
|
783
|
+
ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
if (!ctx.host.security.isTerminalAuthorized(req)) {
|
|
787
|
+
ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
const body = await ctx.host.http.readJsonBody(req);
|
|
791
|
+
if (!isPlainObject4(body) || "subPath" in body || typeof body.theaterId !== "string") {
|
|
792
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
const subject = typeof body.subject === "string" ? body.subject.trim() : "";
|
|
796
|
+
const message = body.message === void 0 ? "" : body.message;
|
|
797
|
+
const amend = body.amend === true;
|
|
798
|
+
if (subject === "" || subject.length > MAX_SUBJECT_LENGTH || typeof message !== "string" || message.length > MAX_BODY_LENGTH || subject.includes("\0") || message.includes("\0")) {
|
|
799
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_message" });
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
const theaterPath = ctx.host.paths.resolveTheaterPath(body.theaterId);
|
|
803
|
+
if (!theaterPath) {
|
|
804
|
+
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
let gitCwd;
|
|
808
|
+
try {
|
|
809
|
+
({ gitCwd } = await resolveGitCwd(theaterPath, body.repoRel));
|
|
810
|
+
} catch (error) {
|
|
811
|
+
if (error instanceof InvalidRepoError) {
|
|
812
|
+
ctx.host.http.writeJson(res, 400, { error: error.code });
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
throw error;
|
|
816
|
+
}
|
|
817
|
+
try {
|
|
818
|
+
if (!amend && !await hasStagedChanges(gitCwd)) {
|
|
819
|
+
ctx.host.http.writeJson(res, 422, { error: "nothing_to_commit" });
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
const trimmedMessage = message.trim();
|
|
823
|
+
const result = await runGit([
|
|
824
|
+
...COMMIT_HARDENING_ARGS,
|
|
825
|
+
"commit",
|
|
826
|
+
"--no-verify",
|
|
827
|
+
...amend ? ["--amend"] : [],
|
|
828
|
+
"-m",
|
|
829
|
+
subject,
|
|
830
|
+
...trimmedMessage ? ["-m", trimmedMessage] : []
|
|
831
|
+
], { cwd: gitCwd });
|
|
832
|
+
const sha = (await runGit(["rev-parse", "HEAD"], { cwd: gitCwd })).stdout.trim();
|
|
833
|
+
ctx.host.http.writeJson(res, 200, { ok: true, sha, summary: result.stdout.split("\n")[0] ?? "" });
|
|
834
|
+
} catch (error) {
|
|
835
|
+
if (error instanceof GitExecutorError) {
|
|
836
|
+
const classified = classifyCommitError(error.stderr) ?? classifyCommitError(error.message);
|
|
837
|
+
if (classified) {
|
|
838
|
+
ctx.host.http.writeJson(res, 422, { error: classified });
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
const locked = classifyWriteError(error.stderr);
|
|
842
|
+
if (locked) {
|
|
843
|
+
ctx.host.http.writeJson(res, 409, { error: locked });
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
if (error.code === "no_git_repo" || error.code === "git_unavailable" || error.code === "timeout") {
|
|
847
|
+
ctx.host.http.writeJson(res, 422, { error: error.code });
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
ctx.host.http.writeJson(res, 500, { error: "git_failed" });
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
throw error;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
function isPlainObject5(value) {
|
|
857
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
858
|
+
}
|
|
605
859
|
function resolveLiteralFilePath(gitCwd, rawPath) {
|
|
606
860
|
const relativePath = path3.normalize(rawPath);
|
|
607
861
|
const resolved = path3.resolve(gitCwd, relativePath);
|
|
@@ -621,7 +875,7 @@ async function handleRepositoryCommitFile(req, res, ctx) {
|
|
|
621
875
|
return;
|
|
622
876
|
}
|
|
623
877
|
const body = await ctx.host.http.readJsonBody(req);
|
|
624
|
-
if (!
|
|
878
|
+
if (!isPlainObject5(body) || "subPath" in body || typeof body.theaterId !== "string" || typeof body.ref !== "string" || typeof body.filePath !== "string") {
|
|
625
879
|
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
626
880
|
return;
|
|
627
881
|
}
|
|
@@ -675,7 +929,7 @@ async function handleRepositoryCommitFile(req, res, ctx) {
|
|
|
675
929
|
}
|
|
676
930
|
|
|
677
931
|
// ../fleet-plugins/repository/server/compare.ts
|
|
678
|
-
function
|
|
932
|
+
function isPlainObject6(value) {
|
|
679
933
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
680
934
|
}
|
|
681
935
|
var FULL_REF_PREFIXES = ["refs/heads/", "refs/remotes/", "refs/tags/"];
|
|
@@ -719,7 +973,7 @@ async function handleRepositoryCompare(req, res, ctx) {
|
|
|
719
973
|
return;
|
|
720
974
|
}
|
|
721
975
|
const body = await ctx.host.http.readJsonBody(req);
|
|
722
|
-
if (!
|
|
976
|
+
if (!isPlainObject6(body) || "subPath" in body) {
|
|
723
977
|
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
724
978
|
return;
|
|
725
979
|
}
|
|
@@ -787,7 +1041,7 @@ async function handleRepositoryCompare(req, res, ctx) {
|
|
|
787
1041
|
throw error;
|
|
788
1042
|
}
|
|
789
1043
|
}
|
|
790
|
-
function
|
|
1044
|
+
function isPlainObject7(value) {
|
|
791
1045
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
792
1046
|
}
|
|
793
1047
|
function resolveLiteralFilePath2(gitCwd, rawPath) {
|
|
@@ -809,7 +1063,7 @@ async function handleRepositoryCompareFile(req, res, ctx) {
|
|
|
809
1063
|
return;
|
|
810
1064
|
}
|
|
811
1065
|
const body = await ctx.host.http.readJsonBody(req);
|
|
812
|
-
if (!
|
|
1066
|
+
if (!isPlainObject7(body) || "subPath" in body || typeof body.theaterId !== "string" || typeof body.base !== "string" || typeof body.head !== "string" || typeof body.filePath !== "string") {
|
|
813
1067
|
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
814
1068
|
return;
|
|
815
1069
|
}
|
|
@@ -873,7 +1127,7 @@ async function handleRepositoryCompareFile(req, res, ctx) {
|
|
|
873
1127
|
var AUTO_FETCH_THROTTLE_MS = 3e5;
|
|
874
1128
|
var autoFetchInFlight = /* @__PURE__ */ new Map();
|
|
875
1129
|
var lastSuccessfulFetchAt = /* @__PURE__ */ new Map();
|
|
876
|
-
function
|
|
1130
|
+
function isPlainObject8(value) {
|
|
877
1131
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
878
1132
|
}
|
|
879
1133
|
function classifyFetchError(stderr) {
|
|
@@ -922,6 +1176,16 @@ function countFetchUpdatedRefs(stderr) {
|
|
|
922
1176
|
}
|
|
923
1177
|
var NoRemoteError = class extends Error {
|
|
924
1178
|
};
|
|
1179
|
+
var NETWORK_HARDENING_ARGS = [
|
|
1180
|
+
"-c",
|
|
1181
|
+
"core.sshCommand=ssh",
|
|
1182
|
+
"-c",
|
|
1183
|
+
"core.gitProxy=none",
|
|
1184
|
+
"-c",
|
|
1185
|
+
"protocol.ext.allow=never",
|
|
1186
|
+
"-c",
|
|
1187
|
+
`core.hooksPath=${process.platform === "win32" ? "NUL" : "/dev/null"}`
|
|
1188
|
+
];
|
|
925
1189
|
var SAFE_CREDENTIAL_HELPERS = /* @__PURE__ */ new Set([
|
|
926
1190
|
"cache",
|
|
927
1191
|
"store",
|
|
@@ -1043,7 +1307,7 @@ async function handleRepositoryFetch(req, res, ctx) {
|
|
|
1043
1307
|
return;
|
|
1044
1308
|
}
|
|
1045
1309
|
const body = await ctx.host.http.readJsonBody(req);
|
|
1046
|
-
if (!
|
|
1310
|
+
if (!isPlainObject8(body) || "subPath" in body || typeof body.theaterId !== "string" || body.mode !== void 0 && body.mode !== "auto") {
|
|
1047
1311
|
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
1048
1312
|
return;
|
|
1049
1313
|
}
|
|
@@ -1103,7 +1367,7 @@ function resolveLogOrder(requested) {
|
|
|
1103
1367
|
if (requested === void 0) return "topo";
|
|
1104
1368
|
return requested === "topo" || requested === "date" ? requested : null;
|
|
1105
1369
|
}
|
|
1106
|
-
function
|
|
1370
|
+
function isPlainObject9(value) {
|
|
1107
1371
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1108
1372
|
}
|
|
1109
1373
|
function isCanonicalRepositoryRef(ref) {
|
|
@@ -1230,7 +1494,7 @@ async function handleRepositoryLog(req, res, ctx) {
|
|
|
1230
1494
|
return;
|
|
1231
1495
|
}
|
|
1232
1496
|
const body = await ctx.host.http.readJsonBody(req);
|
|
1233
|
-
if (!
|
|
1497
|
+
if (!isPlainObject9(body) || "subPath" in body) {
|
|
1234
1498
|
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
1235
1499
|
return;
|
|
1236
1500
|
}
|
|
@@ -1319,6 +1583,145 @@ async function handleRepositoryLog(req, res, ctx) {
|
|
|
1319
1583
|
}
|
|
1320
1584
|
}
|
|
1321
1585
|
|
|
1586
|
+
// ../fleet-plugins/repository/server/remote.ts
|
|
1587
|
+
function isPlainObject10(value) {
|
|
1588
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1589
|
+
}
|
|
1590
|
+
var REMOTE_TIMEOUT_MS = 12e4;
|
|
1591
|
+
function classifyPushError(stderr) {
|
|
1592
|
+
return /non-fast-forward|fetch first|updates were rejected/i.test(stderr) ? "non_fast_forward" : null;
|
|
1593
|
+
}
|
|
1594
|
+
function classifyPullError(stderr) {
|
|
1595
|
+
if (/not possible to fast-forward|need to specify how to reconcile|diverg/i.test(stderr)) return "non_fast_forward";
|
|
1596
|
+
if (/overwritten by merge|Please commit your changes or stash/i.test(stderr)) return "dirty_worktree";
|
|
1597
|
+
return null;
|
|
1598
|
+
}
|
|
1599
|
+
async function resolveRemoteContext(req, res, ctx) {
|
|
1600
|
+
if (req.method !== "POST") {
|
|
1601
|
+
ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
|
|
1602
|
+
return null;
|
|
1603
|
+
}
|
|
1604
|
+
if (!ctx.host.security.isTerminalAuthorized(req)) {
|
|
1605
|
+
ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
|
|
1606
|
+
return null;
|
|
1607
|
+
}
|
|
1608
|
+
const body = await ctx.host.http.readJsonBody(req);
|
|
1609
|
+
if (!isPlainObject10(body) || "subPath" in body || typeof body.theaterId !== "string") {
|
|
1610
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
1611
|
+
return null;
|
|
1612
|
+
}
|
|
1613
|
+
const theaterPath = ctx.host.paths.resolveTheaterPath(body.theaterId);
|
|
1614
|
+
if (!theaterPath) {
|
|
1615
|
+
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
1616
|
+
return null;
|
|
1617
|
+
}
|
|
1618
|
+
let gitCwd;
|
|
1619
|
+
try {
|
|
1620
|
+
({ gitCwd } = await resolveGitCwd(theaterPath, body.repoRel));
|
|
1621
|
+
} catch (error) {
|
|
1622
|
+
if (error instanceof InvalidRepoError) {
|
|
1623
|
+
ctx.host.http.writeJson(res, 400, { error: error.code });
|
|
1624
|
+
return null;
|
|
1625
|
+
}
|
|
1626
|
+
throw error;
|
|
1627
|
+
}
|
|
1628
|
+
try {
|
|
1629
|
+
const branch = (await runGit(["symbolic-ref", "--quiet", "--short", "HEAD"], { cwd: gitCwd, allowExitCodes: [1] })).stdout.trim();
|
|
1630
|
+
if (!branch) {
|
|
1631
|
+
ctx.host.http.writeJson(res, 422, { error: "detached_head" });
|
|
1632
|
+
return null;
|
|
1633
|
+
}
|
|
1634
|
+
return { gitCwd, branch };
|
|
1635
|
+
} catch (error) {
|
|
1636
|
+
writeRemoteFailure(res, ctx, error, () => null);
|
|
1637
|
+
return null;
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
function writeRemoteFailure(res, ctx, error, classify) {
|
|
1641
|
+
if (error instanceof NoRemoteError) {
|
|
1642
|
+
ctx.host.http.writeJson(res, 422, { error: "no_remote" });
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
if (error instanceof GitExecutorError) {
|
|
1646
|
+
if (error.code === "timeout") {
|
|
1647
|
+
ctx.host.http.writeJson(res, 422, { error: "timeout" });
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
const specific = classify(error.stderr);
|
|
1651
|
+
if (specific) {
|
|
1652
|
+
ctx.host.http.writeJson(res, 409, { error: specific });
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
const network = classifyFetchError(error.stderr);
|
|
1656
|
+
if (network) {
|
|
1657
|
+
ctx.host.http.writeJson(res, 422, { error: network });
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
const locked = classifyWriteError(error.stderr);
|
|
1661
|
+
if (locked) {
|
|
1662
|
+
ctx.host.http.writeJson(res, 409, { error: locked });
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
if (error.code === "no_git_repo" || error.code === "git_unavailable") {
|
|
1666
|
+
ctx.host.http.writeJson(res, 422, { error: error.code });
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
ctx.host.http.writeJson(res, 500, { error: "git_failed" });
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
throw error;
|
|
1673
|
+
}
|
|
1674
|
+
async function handleRepositoryPush(req, res, ctx) {
|
|
1675
|
+
const remoteCtx = await resolveRemoteContext(req, res, ctx);
|
|
1676
|
+
if (!remoteCtx) return;
|
|
1677
|
+
const { gitCwd, branch } = remoteCtx;
|
|
1678
|
+
try {
|
|
1679
|
+
const upstreamRemote = (await runGit(["config", "--get", `branch.${branch}.remote`], { cwd: gitCwd, allowExitCodes: [1] })).stdout.trim();
|
|
1680
|
+
const remote = upstreamRemote && upstreamRemote !== "." ? upstreamRemote : await resolveDefaultRemote(gitCwd);
|
|
1681
|
+
if (!remote) throw new NoRemoteError();
|
|
1682
|
+
const credentialArgs = await resolveCredentialHelperArgs(gitCwd);
|
|
1683
|
+
await runGit([
|
|
1684
|
+
...NETWORK_HARDENING_ARGS,
|
|
1685
|
+
...credentialArgs,
|
|
1686
|
+
"push",
|
|
1687
|
+
// repo config의 remote.<name>.receivepack이 로컬 transport에서 명령 실행되므로 표준 명령을 강제한다.
|
|
1688
|
+
"--receive-pack=git-receive-pack",
|
|
1689
|
+
"--no-recurse-submodules",
|
|
1690
|
+
...upstreamRemote ? [] : ["--set-upstream"],
|
|
1691
|
+
remote,
|
|
1692
|
+
`refs/heads/${branch}:refs/heads/${branch}`
|
|
1693
|
+
], { cwd: gitCwd, timeoutMs: REMOTE_TIMEOUT_MS });
|
|
1694
|
+
ctx.host.http.writeJson(res, 200, { ok: true, remote, branch });
|
|
1695
|
+
} catch (error) {
|
|
1696
|
+
writeRemoteFailure(res, ctx, error, classifyPushError);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
async function handleRepositoryPull(req, res, ctx) {
|
|
1700
|
+
const remoteCtx = await resolveRemoteContext(req, res, ctx);
|
|
1701
|
+
if (!remoteCtx) return;
|
|
1702
|
+
const { gitCwd, branch } = remoteCtx;
|
|
1703
|
+
try {
|
|
1704
|
+
const upstreamRemote = (await runGit(["config", "--get", `branch.${branch}.remote`], { cwd: gitCwd, allowExitCodes: [1] })).stdout.trim();
|
|
1705
|
+
if (!upstreamRemote || upstreamRemote === ".") {
|
|
1706
|
+
ctx.host.http.writeJson(res, 422, { error: "no_upstream" });
|
|
1707
|
+
return;
|
|
1708
|
+
}
|
|
1709
|
+
const credentialArgs = await resolveCredentialHelperArgs(gitCwd);
|
|
1710
|
+
await runGit([
|
|
1711
|
+
...NETWORK_HARDENING_ARGS,
|
|
1712
|
+
...credentialArgs,
|
|
1713
|
+
"pull",
|
|
1714
|
+
"--ff-only",
|
|
1715
|
+
"--no-rebase",
|
|
1716
|
+
"--upload-pack=git-upload-pack",
|
|
1717
|
+
"--no-recurse-submodules"
|
|
1718
|
+
], { cwd: gitCwd, timeoutMs: REMOTE_TIMEOUT_MS });
|
|
1719
|
+
ctx.host.http.writeJson(res, 200, { ok: true, remote: upstreamRemote, branch });
|
|
1720
|
+
} catch (error) {
|
|
1721
|
+
writeRemoteFailure(res, ctx, error, classifyPullError);
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1322
1725
|
// ../fleet-plugins/repository/server/refs.ts
|
|
1323
1726
|
function isObject(value) {
|
|
1324
1727
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -1421,7 +1824,7 @@ var HARD_CAP_DEPTH = 8;
|
|
|
1421
1824
|
var NESTED_BRANCH_CAP = 64;
|
|
1422
1825
|
var SCAN_VISIT_BUDGET = 1e4;
|
|
1423
1826
|
var DEFAULT_DEPTH = 3;
|
|
1424
|
-
function
|
|
1827
|
+
function isPlainObject11(value) {
|
|
1425
1828
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1426
1829
|
}
|
|
1427
1830
|
async function resolveRepoBranch(repoDir) {
|
|
@@ -1510,7 +1913,7 @@ async function handleRepositoryRepos(req, res, ctx) {
|
|
|
1510
1913
|
return;
|
|
1511
1914
|
}
|
|
1512
1915
|
const body = await ctx.host.http.readJsonBody(req);
|
|
1513
|
-
if (!
|
|
1916
|
+
if (!isPlainObject11(body) || typeof body.theaterId !== "string") {
|
|
1514
1917
|
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
1515
1918
|
return;
|
|
1516
1919
|
}
|
|
@@ -1589,7 +1992,7 @@ async function handleRepositorySearch(req, res, ctx) {
|
|
|
1589
1992
|
return;
|
|
1590
1993
|
}
|
|
1591
1994
|
const body = await ctx.host.http.readJsonBody(req);
|
|
1592
|
-
if (!
|
|
1995
|
+
if (!isPlainObject12(body) || typeof body.theaterId !== "string" || typeof body.query !== "string" || body.query.trim() === "" || !Number.isInteger(body.limit) || body.limit < 1 || body.limit > 8 || "subPath" in body || "ref" in body) {
|
|
1593
1996
|
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
1594
1997
|
return;
|
|
1595
1998
|
}
|
|
@@ -1643,10 +2046,451 @@ async function handleRepositorySearch(req, res, ctx) {
|
|
|
1643
2046
|
throw error;
|
|
1644
2047
|
}
|
|
1645
2048
|
}
|
|
1646
|
-
function
|
|
2049
|
+
function isPlainObject12(value) {
|
|
1647
2050
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1648
2051
|
}
|
|
1649
|
-
|
|
2052
|
+
|
|
2053
|
+
// ../fleet-plugins/repository/server/stash.ts
|
|
2054
|
+
function isPlainObject13(value) {
|
|
2055
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2056
|
+
}
|
|
2057
|
+
var STASH_NAME_RE = /^stash@\{\d{1,4}\}$/;
|
|
2058
|
+
var MAX_STASH_MESSAGE_LENGTH = 500;
|
|
2059
|
+
var STASH_HARDENING_ARGS = [
|
|
2060
|
+
"-c",
|
|
2061
|
+
`core.hooksPath=${process.platform === "win32" ? "NUL" : "/dev/null"}`
|
|
2062
|
+
];
|
|
2063
|
+
function readStashAction(value) {
|
|
2064
|
+
return value === "save" || value === "apply" || value === "pop" || value === "drop" ? value : null;
|
|
2065
|
+
}
|
|
2066
|
+
function classifyStashError(stderr) {
|
|
2067
|
+
if (/conflict|could not restore untracked files|overwritten by merge/i.test(stderr)) return "stash_conflict";
|
|
2068
|
+
if (/No local changes to save/i.test(stderr)) return "nothing_to_stash";
|
|
2069
|
+
return null;
|
|
2070
|
+
}
|
|
2071
|
+
async function handleRepositoryStash(req, res, ctx) {
|
|
2072
|
+
if (req.method !== "POST") {
|
|
2073
|
+
ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
|
|
2074
|
+
return;
|
|
2075
|
+
}
|
|
2076
|
+
if (!ctx.host.security.isTerminalAuthorized(req)) {
|
|
2077
|
+
ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
|
|
2078
|
+
return;
|
|
2079
|
+
}
|
|
2080
|
+
const body = await ctx.host.http.readJsonBody(req);
|
|
2081
|
+
if (!isPlainObject13(body) || "subPath" in body || typeof body.theaterId !== "string") {
|
|
2082
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
const action = readStashAction(body.action);
|
|
2086
|
+
if (!action) {
|
|
2087
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
2088
|
+
return;
|
|
2089
|
+
}
|
|
2090
|
+
const name = body.name;
|
|
2091
|
+
if (action !== "save" && (typeof name !== "string" || !STASH_NAME_RE.test(name))) {
|
|
2092
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_stash" });
|
|
2093
|
+
return;
|
|
2094
|
+
}
|
|
2095
|
+
const sha = body.sha;
|
|
2096
|
+
if (action !== "save" && (typeof sha !== "string" || !/^[0-9a-f]{7,40}$/i.test(sha))) {
|
|
2097
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_stash" });
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
const message = body.message === void 0 ? "" : body.message;
|
|
2101
|
+
if (typeof message !== "string" || message.length > MAX_STASH_MESSAGE_LENGTH || message.includes("\0") || message.trimStart().startsWith("-")) {
|
|
2102
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
const theaterPath = ctx.host.paths.resolveTheaterPath(body.theaterId);
|
|
2106
|
+
if (!theaterPath) {
|
|
2107
|
+
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
2108
|
+
return;
|
|
2109
|
+
}
|
|
2110
|
+
let gitCwd;
|
|
2111
|
+
try {
|
|
2112
|
+
({ gitCwd } = await resolveGitCwd(theaterPath, body.repoRel));
|
|
2113
|
+
} catch (error) {
|
|
2114
|
+
if (error instanceof InvalidRepoError) {
|
|
2115
|
+
ctx.host.http.writeJson(res, 400, { error: error.code });
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
throw error;
|
|
2119
|
+
}
|
|
2120
|
+
try {
|
|
2121
|
+
if (action === "save") {
|
|
2122
|
+
const trimmed = message.trim();
|
|
2123
|
+
const result = await runGit([
|
|
2124
|
+
...STASH_HARDENING_ARGS,
|
|
2125
|
+
"stash",
|
|
2126
|
+
"push",
|
|
2127
|
+
"-u",
|
|
2128
|
+
...trimmed ? ["-m", trimmed] : []
|
|
2129
|
+
], { cwd: gitCwd });
|
|
2130
|
+
if (/No local changes to save/i.test(result.stdout)) {
|
|
2131
|
+
ctx.host.http.writeJson(res, 422, { error: "nothing_to_stash" });
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
ctx.host.http.writeJson(res, 200, { ok: true });
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
let resolved;
|
|
2138
|
+
try {
|
|
2139
|
+
resolved = (await runGit(["rev-parse", "--verify", `${name}`], { cwd: gitCwd })).stdout.trim();
|
|
2140
|
+
} catch (error) {
|
|
2141
|
+
if (error instanceof GitExecutorError && error.code === "non_zero_exit") {
|
|
2142
|
+
ctx.host.http.writeJson(res, 409, { error: "stash_moved" });
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2145
|
+
throw error;
|
|
2146
|
+
}
|
|
2147
|
+
if (!resolved.toLowerCase().startsWith(sha.toLowerCase())) {
|
|
2148
|
+
ctx.host.http.writeJson(res, 409, { error: "stash_moved" });
|
|
2149
|
+
return;
|
|
2150
|
+
}
|
|
2151
|
+
await runGit([...STASH_HARDENING_ARGS, "stash", action, name], { cwd: gitCwd });
|
|
2152
|
+
ctx.host.http.writeJson(res, 200, { ok: true });
|
|
2153
|
+
} catch (error) {
|
|
2154
|
+
if (error instanceof GitExecutorError) {
|
|
2155
|
+
const classified = classifyStashError(error.stderr);
|
|
2156
|
+
if (classified) {
|
|
2157
|
+
ctx.host.http.writeJson(res, 409, { error: classified });
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
2160
|
+
const locked = classifyWriteError(error.stderr);
|
|
2161
|
+
if (locked) {
|
|
2162
|
+
ctx.host.http.writeJson(res, 409, { error: locked });
|
|
2163
|
+
return;
|
|
2164
|
+
}
|
|
2165
|
+
if (error.code === "no_git_repo" || error.code === "git_unavailable" || error.code === "timeout") {
|
|
2166
|
+
ctx.host.http.writeJson(res, 422, { error: error.code });
|
|
2167
|
+
return;
|
|
2168
|
+
}
|
|
2169
|
+
ctx.host.http.writeJson(res, 500, { error: "git_failed" });
|
|
2170
|
+
return;
|
|
2171
|
+
}
|
|
2172
|
+
throw error;
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
// ../fleet-plugins/repository/server/status.ts
|
|
2177
|
+
function isPlainObject14(value) {
|
|
2178
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2179
|
+
}
|
|
2180
|
+
function parseNumstat(stdout) {
|
|
2181
|
+
const map = /* @__PURE__ */ new Map();
|
|
2182
|
+
const records = stdout.split("\0");
|
|
2183
|
+
for (let index = 0; index < records.length; index += 1) {
|
|
2184
|
+
const record = records[index];
|
|
2185
|
+
const parts = record.split(" ");
|
|
2186
|
+
if (parts.length < 3) continue;
|
|
2187
|
+
let filePath = parts[2];
|
|
2188
|
+
if (filePath === "" && index + 2 < records.length) {
|
|
2189
|
+
filePath = records[index + 2];
|
|
2190
|
+
index += 2;
|
|
2191
|
+
}
|
|
2192
|
+
if (!filePath) continue;
|
|
2193
|
+
map.set(filePath, {
|
|
2194
|
+
additions: Number.parseInt(parts[0] ?? "0", 10) || 0,
|
|
2195
|
+
deletions: Number.parseInt(parts[1] ?? "0", 10) || 0
|
|
2196
|
+
});
|
|
2197
|
+
}
|
|
2198
|
+
return map;
|
|
2199
|
+
}
|
|
2200
|
+
var STATUS_CHARS = /* @__PURE__ */ new Set(["M", "A", "D", "R", "T"]);
|
|
2201
|
+
function toEntry(statusChar, filePath, oldPath, nums) {
|
|
2202
|
+
if (!STATUS_CHARS.has(statusChar)) return null;
|
|
2203
|
+
const stat = nums.get(filePath) ?? { additions: 0, deletions: 0 };
|
|
2204
|
+
return {
|
|
2205
|
+
path: filePath,
|
|
2206
|
+
...oldPath ? { oldPath } : {},
|
|
2207
|
+
status: statusChar,
|
|
2208
|
+
...stat
|
|
2209
|
+
};
|
|
2210
|
+
}
|
|
2211
|
+
function parseStatusV2(stdout, stagedNums, unstagedNums) {
|
|
2212
|
+
const staged = [];
|
|
2213
|
+
const unstaged = [];
|
|
2214
|
+
const records = stdout.split("\0");
|
|
2215
|
+
for (let index = 0; index < records.length; index += 1) {
|
|
2216
|
+
const record = records[index];
|
|
2217
|
+
if (!record) continue;
|
|
2218
|
+
const kind = record.charAt(0);
|
|
2219
|
+
if (kind === "1" || kind === "2") {
|
|
2220
|
+
const fields = record.split(" ");
|
|
2221
|
+
const xy = fields[1] ?? "..";
|
|
2222
|
+
const filePath = fields.slice(kind === "1" ? 8 : 9).join(" ");
|
|
2223
|
+
if (!filePath) continue;
|
|
2224
|
+
let oldPath;
|
|
2225
|
+
if (kind === "2" && index + 1 < records.length) {
|
|
2226
|
+
oldPath = records[index + 1] || void 0;
|
|
2227
|
+
index += 1;
|
|
2228
|
+
}
|
|
2229
|
+
const stagedChar = xy.charAt(0);
|
|
2230
|
+
const unstagedChar = xy.charAt(1);
|
|
2231
|
+
if (stagedChar !== ".") {
|
|
2232
|
+
const entry = toEntry(stagedChar, filePath, oldPath, stagedNums);
|
|
2233
|
+
if (entry) staged.push(entry);
|
|
2234
|
+
}
|
|
2235
|
+
if (unstagedChar !== ".") {
|
|
2236
|
+
const entry = toEntry(unstagedChar === "R" ? "R" : unstagedChar, filePath, void 0, unstagedNums);
|
|
2237
|
+
if (entry) unstaged.push(entry);
|
|
2238
|
+
}
|
|
2239
|
+
} else if (kind === "?") {
|
|
2240
|
+
const filePath = record.slice(2);
|
|
2241
|
+
if (filePath) unstaged.push({ path: filePath, status: "U", additions: 0, deletions: 0 });
|
|
2242
|
+
} else if (kind === "u") {
|
|
2243
|
+
const filePath = record.split(" ").slice(10).join(" ");
|
|
2244
|
+
if (filePath) unstaged.push({ path: filePath, status: "U", conflicted: true, additions: 0, deletions: 0 });
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
return { staged, unstaged };
|
|
2248
|
+
}
|
|
2249
|
+
async function handleRepositoryStatus(req, res, ctx) {
|
|
2250
|
+
if (req.method !== "POST") {
|
|
2251
|
+
ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
if (!ctx.host.security.isTerminalAuthorized(req)) {
|
|
2255
|
+
ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
const body = await ctx.host.http.readJsonBody(req);
|
|
2259
|
+
if (!isPlainObject14(body) || "subPath" in body || typeof body.theaterId !== "string") {
|
|
2260
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
2263
|
+
const theaterPath = ctx.host.paths.resolveTheaterPath(body.theaterId);
|
|
2264
|
+
if (!theaterPath) {
|
|
2265
|
+
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
2266
|
+
return;
|
|
2267
|
+
}
|
|
2268
|
+
let gitCwd;
|
|
2269
|
+
try {
|
|
2270
|
+
({ gitCwd } = await resolveGitCwd(theaterPath, body.repoRel));
|
|
2271
|
+
} catch (error) {
|
|
2272
|
+
if (error instanceof InvalidRepoError) {
|
|
2273
|
+
ctx.host.http.writeJson(res, 400, { error: error.code });
|
|
2274
|
+
return;
|
|
2275
|
+
}
|
|
2276
|
+
throw error;
|
|
2277
|
+
}
|
|
2278
|
+
try {
|
|
2279
|
+
const [statusResult, stagedNumsResult, unstagedNumsResult] = await Promise.all([
|
|
2280
|
+
runGit(["status", "--porcelain=v2", "--untracked-files=all", "-z"], { cwd: gitCwd }),
|
|
2281
|
+
// 스테이지 열의 +/− — unborn HEAD에서는 비교 대상이 없어 실패하므로 빈 목록으로 강등한다.
|
|
2282
|
+
runGit(["diff", "--cached", "--numstat", "-z", "--", "."], { cwd: gitCwd }).catch(() => ({ stdout: "", truncated: false })),
|
|
2283
|
+
runGit(["diff", "--numstat", "-z", "--", "."], { cwd: gitCwd }).catch(() => ({ stdout: "", truncated: false }))
|
|
2284
|
+
]);
|
|
2285
|
+
const parsed = parseStatusV2(statusResult.stdout, parseNumstat(stagedNumsResult.stdout), parseNumstat(unstagedNumsResult.stdout));
|
|
2286
|
+
const payload = { staged: parsed.staged, unstaged: parsed.unstaged, truncated: statusResult.truncated };
|
|
2287
|
+
ctx.host.http.writeJson(res, 200, payload);
|
|
2288
|
+
} catch (error) {
|
|
2289
|
+
if (error instanceof GitExecutorError) {
|
|
2290
|
+
if (error.code === "no_git_repo" || error.code === "git_unavailable") {
|
|
2291
|
+
ctx.host.http.writeJson(res, 422, { error: error.code });
|
|
2292
|
+
return;
|
|
2293
|
+
}
|
|
2294
|
+
ctx.host.http.writeJson(res, 500, { error: "git_failed" });
|
|
2295
|
+
return;
|
|
2296
|
+
}
|
|
2297
|
+
throw error;
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
function isPlainObject15(value) {
|
|
2301
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2302
|
+
}
|
|
2303
|
+
function parseTreeEntries(stdout) {
|
|
2304
|
+
const entries = [];
|
|
2305
|
+
for (const record of stdout.split("\0")) {
|
|
2306
|
+
if (!record) continue;
|
|
2307
|
+
const tab = record.indexOf(" ");
|
|
2308
|
+
if (tab < 0) continue;
|
|
2309
|
+
const meta = record.slice(0, tab).split(" ");
|
|
2310
|
+
const entryPath = record.slice(tab + 1);
|
|
2311
|
+
const kind = meta[1];
|
|
2312
|
+
if (!entryPath || kind !== "tree" && kind !== "blob") continue;
|
|
2313
|
+
const trimmed = entryPath.endsWith("/") ? entryPath.slice(0, -1) : entryPath;
|
|
2314
|
+
entries.push({ path: entryPath, name: trimmed.slice(trimmed.lastIndexOf("/") + 1), kind });
|
|
2315
|
+
}
|
|
2316
|
+
return entries.sort((a, b) => a.kind === b.kind ? a.name.localeCompare(b.name) : a.kind === "tree" ? -1 : 1);
|
|
2317
|
+
}
|
|
2318
|
+
function isSafeTreeDirPath(dirPath) {
|
|
2319
|
+
if (dirPath === "") return true;
|
|
2320
|
+
if (path3.posix.isAbsolute(dirPath) || path3.isAbsolute(dirPath)) return false;
|
|
2321
|
+
if (dirPath.includes("\0") || dirPath.startsWith("-") || dirPath.startsWith(":")) return false;
|
|
2322
|
+
const normalized = path3.posix.normalize(dirPath);
|
|
2323
|
+
return normalized !== ".." && !normalized.startsWith("../");
|
|
2324
|
+
}
|
|
2325
|
+
async function handleRepositoryTree(req, res, ctx) {
|
|
2326
|
+
if (req.method !== "POST") {
|
|
2327
|
+
ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
|
|
2328
|
+
return;
|
|
2329
|
+
}
|
|
2330
|
+
if (!ctx.host.security.isTerminalAuthorized(req)) {
|
|
2331
|
+
ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
|
|
2332
|
+
return;
|
|
2333
|
+
}
|
|
2334
|
+
const body = await ctx.host.http.readJsonBody(req);
|
|
2335
|
+
if (!isPlainObject15(body) || "subPath" in body || typeof body.theaterId !== "string") {
|
|
2336
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2339
|
+
const ref = body.ref;
|
|
2340
|
+
if (typeof ref !== "string" || !REF_RE.test(ref)) {
|
|
2341
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_ref" });
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
const dirPath = body.dirPath === void 0 ? "" : body.dirPath;
|
|
2345
|
+
if (typeof dirPath !== "string" || !isSafeTreeDirPath(dirPath)) {
|
|
2346
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
const theaterPath = ctx.host.paths.resolveTheaterPath(body.theaterId);
|
|
2350
|
+
if (!theaterPath) {
|
|
2351
|
+
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2354
|
+
let gitCwd;
|
|
2355
|
+
try {
|
|
2356
|
+
({ gitCwd } = await resolveGitCwd(theaterPath, body.repoRel));
|
|
2357
|
+
} catch (error) {
|
|
2358
|
+
if (error instanceof InvalidRepoError) {
|
|
2359
|
+
ctx.host.http.writeJson(res, 400, { error: error.code });
|
|
2360
|
+
return;
|
|
2361
|
+
}
|
|
2362
|
+
throw error;
|
|
2363
|
+
}
|
|
2364
|
+
try {
|
|
2365
|
+
const args = ["ls-tree", "-z", "--full-tree", ref, "--", dirPath === "" ? "." : `${dirPath}/`];
|
|
2366
|
+
const result = await runGit(args, { cwd: gitCwd });
|
|
2367
|
+
const payload = { entries: parseTreeEntries(result.stdout), truncated: result.truncated };
|
|
2368
|
+
ctx.host.http.writeJson(res, 200, payload);
|
|
2369
|
+
} catch (error) {
|
|
2370
|
+
if (error instanceof GitExecutorError) {
|
|
2371
|
+
if (error.code === "no_git_repo" || error.code === "git_unavailable") {
|
|
2372
|
+
ctx.host.http.writeJson(res, 422, { error: error.code });
|
|
2373
|
+
return;
|
|
2374
|
+
}
|
|
2375
|
+
if (error.code === "non_zero_exit" && /not a tree object|Not a valid object name|bad object/i.test(error.stderr)) {
|
|
2376
|
+
ctx.host.http.writeJson(res, 404, { error: "ref_not_found" });
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
2379
|
+
ctx.host.http.writeJson(res, 500, { error: "git_failed" });
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
2382
|
+
throw error;
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
function isPlainObject16(value) {
|
|
2386
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2387
|
+
}
|
|
2388
|
+
async function fileExists(candidate) {
|
|
2389
|
+
try {
|
|
2390
|
+
await fs2.stat(candidate);
|
|
2391
|
+
return true;
|
|
2392
|
+
} catch {
|
|
2393
|
+
return false;
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
function parseAheadBehind(stdout) {
|
|
2397
|
+
const match = /^(\d+)\s+(\d+)$/.exec(stdout.trim());
|
|
2398
|
+
if (!match) return null;
|
|
2399
|
+
return { behind: Number.parseInt(match[1], 10), ahead: Number.parseInt(match[2], 10) };
|
|
2400
|
+
}
|
|
2401
|
+
async function handleRepositoryWorkstate(req, res, ctx) {
|
|
2402
|
+
if (req.method !== "POST") {
|
|
2403
|
+
ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
|
|
2404
|
+
return;
|
|
2405
|
+
}
|
|
2406
|
+
if (!ctx.host.security.isTerminalAuthorized(req)) {
|
|
2407
|
+
ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
|
|
2408
|
+
return;
|
|
2409
|
+
}
|
|
2410
|
+
const body = await ctx.host.http.readJsonBody(req);
|
|
2411
|
+
if (!isPlainObject16(body) || "subPath" in body || typeof body.theaterId !== "string") {
|
|
2412
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
2413
|
+
return;
|
|
2414
|
+
}
|
|
2415
|
+
const theaterPath = ctx.host.paths.resolveTheaterPath(body.theaterId);
|
|
2416
|
+
if (!theaterPath) {
|
|
2417
|
+
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
2418
|
+
return;
|
|
2419
|
+
}
|
|
2420
|
+
let gitCwd;
|
|
2421
|
+
try {
|
|
2422
|
+
({ gitCwd } = await resolveGitCwd(theaterPath, body.repoRel));
|
|
2423
|
+
} catch (error) {
|
|
2424
|
+
if (error instanceof InvalidRepoError) {
|
|
2425
|
+
ctx.host.http.writeJson(res, 400, { error: error.code });
|
|
2426
|
+
return;
|
|
2427
|
+
}
|
|
2428
|
+
throw error;
|
|
2429
|
+
}
|
|
2430
|
+
try {
|
|
2431
|
+
const [gitDirResult, headResult, headShaResult] = await Promise.all([
|
|
2432
|
+
runGit(["rev-parse", "--absolute-git-dir"], { cwd: gitCwd }),
|
|
2433
|
+
runGit(["symbolic-ref", "--quiet", "--short", "HEAD"], { cwd: gitCwd, allowExitCodes: [1] }),
|
|
2434
|
+
runGit(["rev-parse", "--quiet", "--verify", "HEAD"], { cwd: gitCwd, allowExitCodes: [1] })
|
|
2435
|
+
]);
|
|
2436
|
+
const gitDir = path3.resolve(gitCwd, gitDirResult.stdout.trim());
|
|
2437
|
+
const headBranch = headResult.stdout.trim() || null;
|
|
2438
|
+
const headSha = headShaResult.stdout.trim() || null;
|
|
2439
|
+
const [indexLock, merge, rebaseMerge, rebaseApply, cherryPick] = await Promise.all([
|
|
2440
|
+
fileExists(path3.join(gitDir, "index.lock")),
|
|
2441
|
+
fileExists(path3.join(gitDir, "MERGE_HEAD")),
|
|
2442
|
+
fileExists(path3.join(gitDir, "rebase-merge")),
|
|
2443
|
+
fileExists(path3.join(gitDir, "rebase-apply")),
|
|
2444
|
+
fileExists(path3.join(gitDir, "CHERRY_PICK_HEAD"))
|
|
2445
|
+
]);
|
|
2446
|
+
const inProgress = merge ? "merge" : rebaseMerge || rebaseApply ? "rebase" : cherryPick ? "cherry-pick" : null;
|
|
2447
|
+
let upstream = null;
|
|
2448
|
+
let ahead = null;
|
|
2449
|
+
let behind = null;
|
|
2450
|
+
if (headBranch) {
|
|
2451
|
+
const upstreamResult = await runGit(
|
|
2452
|
+
["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"],
|
|
2453
|
+
{ cwd: gitCwd, allowExitCodes: [128] }
|
|
2454
|
+
);
|
|
2455
|
+
upstream = upstreamResult.stdout.trim() || null;
|
|
2456
|
+
if (upstream) {
|
|
2457
|
+
const counts = parseAheadBehind((await runGit(["rev-list", "--left-right", "--count", "@{upstream}...HEAD"], { cwd: gitCwd })).stdout);
|
|
2458
|
+
ahead = counts?.ahead ?? null;
|
|
2459
|
+
behind = counts?.behind ?? null;
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
let realGitCwd = gitCwd;
|
|
2463
|
+
try {
|
|
2464
|
+
realGitCwd = await fs2.realpath(gitCwd);
|
|
2465
|
+
} catch {
|
|
2466
|
+
}
|
|
2467
|
+
const stationedOperations = [];
|
|
2468
|
+
for (const operation of ctx.host.operations.list()) {
|
|
2469
|
+
const rawCwd = operation.payload?.cwd;
|
|
2470
|
+
const cwd = typeof rawCwd === "string" ? rawCwd : theaterPath;
|
|
2471
|
+
let realCwd;
|
|
2472
|
+
try {
|
|
2473
|
+
realCwd = await fs2.realpath(cwd);
|
|
2474
|
+
} catch {
|
|
2475
|
+
continue;
|
|
2476
|
+
}
|
|
2477
|
+
if (isPathContained(realGitCwd, realCwd)) stationedOperations.push({ id: operation.id, title: operation.title });
|
|
2478
|
+
}
|
|
2479
|
+
const payload = { indexLock, inProgress, headBranch, headSha, upstream, ahead, behind, stationedOperations };
|
|
2480
|
+
ctx.host.http.writeJson(res, 200, payload);
|
|
2481
|
+
} catch (error) {
|
|
2482
|
+
if (error instanceof GitExecutorError) {
|
|
2483
|
+
if (error.code === "no_git_repo" || error.code === "git_unavailable") {
|
|
2484
|
+
ctx.host.http.writeJson(res, 422, { error: error.code });
|
|
2485
|
+
return;
|
|
2486
|
+
}
|
|
2487
|
+
ctx.host.http.writeJson(res, 500, { error: "git_failed" });
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2490
|
+
throw error;
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
function isPlainObject17(value) {
|
|
1650
2494
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1651
2495
|
}
|
|
1652
2496
|
async function handleRepositoryWorktrees(req, res, ctx) {
|
|
@@ -1659,7 +2503,7 @@ async function handleRepositoryWorktrees(req, res, ctx) {
|
|
|
1659
2503
|
return;
|
|
1660
2504
|
}
|
|
1661
2505
|
const body = await ctx.host.http.readJsonBody(req);
|
|
1662
|
-
if (!
|
|
2506
|
+
if (!isPlainObject17(body) || "subPath" in body || typeof body.theaterId !== "string") {
|
|
1663
2507
|
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
1664
2508
|
return;
|
|
1665
2509
|
}
|
|
@@ -1776,6 +2620,46 @@ var routes_default = definePlugin({
|
|
|
1776
2620
|
await handleRepositoryCompareFile(req, res, ctx);
|
|
1777
2621
|
return true;
|
|
1778
2622
|
}, { method: "POST", path: "", summary: "Compare repository files.", category: "Repository Plugin", gate: "origin-write", transport: "http" });
|
|
2623
|
+
registerRouter(ctx, "tree", async ({ req, res }) => {
|
|
2624
|
+
await handleRepositoryTree(req, res, ctx);
|
|
2625
|
+
return true;
|
|
2626
|
+
}, { method: "POST", path: "", summary: "List one folder of a commit tree.", category: "Repository Plugin", gate: "origin-write", transport: "http" });
|
|
2627
|
+
registerRouter(ctx, "workstate", async ({ req, res }) => {
|
|
2628
|
+
await handleRepositoryWorkstate(req, res, ctx);
|
|
2629
|
+
return true;
|
|
2630
|
+
}, { method: "POST", path: "", summary: "Report write-safety state for a checkout.", category: "Repository Plugin", gate: "origin-write", transport: "http" });
|
|
2631
|
+
registerRouter(ctx, "status", async ({ req, res }) => {
|
|
2632
|
+
await handleRepositoryStatus(req, res, ctx);
|
|
2633
|
+
return true;
|
|
2634
|
+
}, { method: "POST", path: "", summary: "List staged and unstaged repository files.", category: "Repository Plugin", gate: "origin-write", transport: "http" });
|
|
2635
|
+
registerRouter(ctx, "stage", async ({ req, res }) => {
|
|
2636
|
+
await handleRepositoryStage(req, res, ctx);
|
|
2637
|
+
return true;
|
|
2638
|
+
}, { method: "POST", path: "", summary: "Stage repository files.", category: "Repository Plugin", gate: "origin-write", transport: "http" });
|
|
2639
|
+
registerRouter(ctx, "unstage", async ({ req, res }) => {
|
|
2640
|
+
await handleRepositoryUnstage(req, res, ctx);
|
|
2641
|
+
return true;
|
|
2642
|
+
}, { method: "POST", path: "", summary: "Unstage repository files.", category: "Repository Plugin", gate: "origin-write", transport: "http" });
|
|
2643
|
+
registerRouter(ctx, "discard", async ({ req, res }) => {
|
|
2644
|
+
await handleRepositoryDiscard(req, res, ctx);
|
|
2645
|
+
return true;
|
|
2646
|
+
}, { method: "POST", path: "", summary: "Discard working-tree changes for chosen files.", category: "Repository Plugin", gate: "origin-write", transport: "http" });
|
|
2647
|
+
registerRouter(ctx, "commit-create", async ({ req, res }) => {
|
|
2648
|
+
await handleRepositoryCommitCreate(req, res, ctx);
|
|
2649
|
+
return true;
|
|
2650
|
+
}, { method: "POST", path: "", summary: "Create or amend a commit from the staged files.", category: "Repository Plugin", gate: "origin-write", transport: "http" });
|
|
2651
|
+
registerRouter(ctx, "stash", async ({ req, res }) => {
|
|
2652
|
+
await handleRepositoryStash(req, res, ctx);
|
|
2653
|
+
return true;
|
|
2654
|
+
}, { method: "POST", path: "", summary: "Save, apply, pop, or drop a stash.", category: "Repository Plugin", gate: "origin-write", transport: "http" });
|
|
2655
|
+
registerRouter(ctx, "push", async ({ req, res }) => {
|
|
2656
|
+
await handleRepositoryPush(req, res, ctx);
|
|
2657
|
+
return true;
|
|
2658
|
+
}, { method: "POST", path: "", summary: "Push the current branch to its upstream.", category: "Repository Plugin", gate: "origin-write", transport: "http" });
|
|
2659
|
+
registerRouter(ctx, "pull", async ({ req, res }) => {
|
|
2660
|
+
await handleRepositoryPull(req, res, ctx);
|
|
2661
|
+
return true;
|
|
2662
|
+
}, { method: "POST", path: "", summary: "Fast-forward the current branch from its upstream.", category: "Repository Plugin", gate: "origin-write", transport: "http" });
|
|
1779
2663
|
}
|
|
1780
2664
|
});
|
|
1781
2665
|
|