GitRoot
Craft your forge, Build your project, Grow your community freely
1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
2//
3// SPDX-License-Identifier: EUPL-1.2
4
5import { JSON } from "json-as";
6import {
7 Commit,
8 File,
9 InitKind,
10 Plugin,
11 PluginRun,
12 PluginRunWhen,
13 PluginWrite,
14 PluginWriteRight,
15 PluginWriteRightCan,
16 Register,
17 ReportLevel,
18 Server,
19} from "plugin/server";
20import { getInitShContent } from "./embedded-data";
21
22export * from "plugin/exports";
23
24@json
25class Conf {
26 static default(): JSON.Raw {
27 return JSON.Raw.from(JSON.stringify<Conf>(new Conf()));
28 }
29}
30
31const defaultRun: PluginRun = new PluginRun(
32 "**/*",
33 ["*"],
34 [PluginRunWhen.Add, PluginRunWhen.Mod, PluginRunWhen.Del],
35 "",
36 [],
37 new PluginWrite(
38 [
39 new PluginWriteRight(".gitroot/init.sh", [
40 PluginWriteRightCan.Add,
41 PluginWriteRightCan.Mod,
42 PluginWriteRightCan.Del,
43 ]),
44 new PluginWriteRight(".gitroot/allowed_signers", [
45 PluginWriteRightCan.Add,
46 PluginWriteRightCan.Mod,
47 PluginWriteRightCan.Del,
48 ]),
49 ],
50 [],
51 [],
52 [],
53 ),
54 Conf.default(),
55);
56
57class Stigma implements Plugin {
58 private server: Server;
59 private conf: Conf | null = null;
60 private goodCommits: Array<Commit> = new Array<Commit>();
61 private notSignedCommits: Array<Commit> = new Array<Commit>();
62 private badCommits: Array<Commit> = new Array<Commit>();
63
64 constructor(server: Server) {
65 this.server = server;
66 }
67
68 init(repoName: string, initKind: InitKind, serializedConf: string): void {
69 this.conf = JSON.parse<Conf>(serializedConf);
70 let needCommit = false;
71 if (!this.server.worktree().exists(".gitroot/init.sh")) {
72 needCommit = true;
73 this.server
74 .worktree()
75 .writeContent(".gitroot/init.sh", getInitShContent());
76 }
77 if (!this.server.worktree().exists(".gitroot/allowed_signers")) {
78 needCommit = true;
79 this.server.log("write allowed_signers during " + initKind);
80 this.server.worktree().writeContent(".gitroot/allowed_signers", "");
81 } else {
82 this.server.log("not write allowed_signers during " + initKind);
83 }
84 if (needCommit) {
85 this.server.commitAllIfNeeded("init stigma plugin");
86 }
87 return;
88 }
89
90 startCommit(commit: Commit): void {
91 if (commit.isSigned && commit.isValidSignature) {
92 this.server.log("Signed commit for " + commit.committerEmail);
93 this.goodCommits.push(commit);
94 } else if (commit.isSigned === false) {
95 this.server.log("Not signed commit for " + commit.committerEmail);
96 this.notSignedCommits.push(commit);
97 } else if (commit.isValidSignature === false) {
98 this.server.log("Bad signed commit for " + commit.committerEmail);
99 this.badCommits.push(commit);
100 }
101 return;
102 }
103
104 addFile(file: File): void {
105 return;
106 }
107
108 modFile(file: File): void {
109 return;
110 }
111
112 delFile(file: File): void {
113 return;
114 }
115
116 endCommit(commit: Commit): void {
117 return;
118 }
119
120 finish(): void {
121 if (this.badCommits.length > 0) {
122 this.server.report(ReportLevel.Error, ["Commits are bad signed!"]);
123 }
124 if (this.notSignedCommits.length > 0) {
125 this.server.report(ReportLevel.Warning, ["Commits are not signed!"]);
126 }
127 if (this.goodCommits.length > 0) {
128 let content =
129 this.server.worktree().readAll(".gitroot/allowed_signers") || "";
130 for (let i = 0; i < this.goodCommits.length; i++) {
131 const c = this.goodCommits[i];
132 if (!content.includes(c.signingKey)) {
133 this.server.log("Want to add for " + c.committerEmail);
134 content = content + `${c.committerEmail} ${c.signingKey}\n`;
135 }
136 }
137 this.server.worktree().writeContent(".gitroot/allowed_signers", content);
138 this.server.commitAllIfNeeded("added signers");
139 }
140 this.conf = null;
141 this.goodCommits = [];
142 this.notSignedCommits = [];
143 this.badCommits = [];
144 return;
145 }
146}
147
148function build(server: Server): Plugin {
149 return new Stigma(server);
150}
151
152export function install(): void {
153 Register([defaultRun], build);
154}