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: MIT
4
5package gitroot
6
7// #include <stdlib.h>
8import "C"
9
10import (
11 "encoding/json"
12 "errors"
13 "runtime"
14
15 "github.com/tidwall/gjson"
16 "gitroot.dev/libs/golang/plugin/model"
17)
18
19type PluginFactory = func(server model.Server) model.Plugin
20
21type pServer struct {
22 worktree *model.GrFs
23 webcontent *model.GrFs
24 cache *model.GrFs
25 plugin model.Plugin
26 pluginOpts model.PluginOption
27 run []model.PluginRun
28 atLeastOneChange bool
29 exportedFuncs map[string]func(args map[string]string) (map[string]string, error)
30}
31
32var server = &pServer{
33 atLeastOneChange: false,
34 pluginOpts: model.PluginOption{},
35 exportedFuncs: map[string]func(args map[string]string) (map[string]string, error){},
36}
37
38func Register(run []model.PluginRun, p PluginFactory) {
39 server.run = run
40 server.plugin = p(server)
41}
42
43func (s *pServer) PluginOption(opts ...model.PluginOptionWith) {
44 for _, opt := range opts {
45 server.pluginOpts = opt(server.pluginOpts)
46 }
47}
48
49func (s *pServer) ForgeConf() (model.ForgeConf, error) {
50 forgeConf := model.ForgeConf{}
51 ptrSize := _forgeConf()
52 if ptrSize == 0 {
53 return forgeConf, errors.New("can't make diff")
54 }
55 ptrDiff := uint32(ptrSize >> 32)
56 sizeDiff := uint32(ptrSize)
57 forgeConfJson := gjson.Parse(ptrToString(ptrDiff, sizeDiff))
58 return model.ForgeConf{
59 Domain: forgeConfJson.Get("domain").String(),
60 ExternalSshAddr: forgeConfJson.Get("externalSshAddr").String(),
61 ExternalHttpAddr: forgeConfJson.Get("externalHttpAddr").String(),
62 RootRepositoryName: forgeConfJson.Get("rootRepositoryName").String(),
63 }, nil
64}
65
66func (s *pServer) Worktree() *model.GrFs {
67 if s.worktree == nil {
68 s.worktree = model.NewGrFs(model.WORKTREE, s)
69 }
70 return s.worktree
71}
72
73func (s *pServer) Webcontent() *model.GrFs {
74 if s.webcontent == nil {
75 s.webcontent = model.NewGrFs(model.WEBCONTENT, s)
76 }
77 return s.webcontent
78}
79
80func (s *pServer) Cache() *model.GrFs {
81 if s.cache == nil {
82 s.cache = model.NewGrFs(model.CACHE, s)
83 }
84 return s.cache
85}
86
87// CopyFile implements [model.ServerNeeded].
88func (s *pServer) CopyFile(fromFs model.FsBase, fromPath string, toFs model.FsBase, toPath string) error {
89 s.atLeastOneChange = true
90 ptr, size := stringToPtr(string(fromFs))
91 ptr2, size2 := stringToPtr(fromPath)
92 ptr3, size3 := stringToPtr(string(toFs))
93 ptr4, size4 := stringToPtr(toPath)
94 resPtrSize := _copyFile(ptr, size, ptr2, size2, ptr3, size3, ptr4, size4)
95 runtime.KeepAlive(fromFs)
96 runtime.KeepAlive(fromPath)
97 runtime.KeepAlive(toFs)
98 runtime.KeepAlive(toPath)
99 return ptrSizeToError(resPtrSize)
100}
101
102// DeleteFile implements [model.ServerNeeded].
103func (s *pServer) DeleteFile(fromFs model.FsBase, filename string) error {
104 s.atLeastOneChange = true
105 ptr, size := stringToPtr(string(fromFs))
106 ptr2, size2 := stringToPtr(filename)
107 resPtrSize := _deleteFile(ptr, size, ptr2, size2)
108 runtime.KeepAlive(fromFs)
109 runtime.KeepAlive(filename)
110 return ptrSizeToError(resPtrSize)
111}
112
113// MoveFile implements [model.ServerNeeded].
114func (s *pServer) MoveFile(fromFs model.FsBase, fromPath string, toFs model.FsBase, toPath string) error {
115 s.atLeastOneChange = true
116 ptr, size := stringToPtr(string(fromFs))
117 ptr2, size2 := stringToPtr(fromPath)
118 ptr3, size3 := stringToPtr(string(toFs))
119 ptr4, size4 := stringToPtr(toPath)
120 resPtrSize := _moveFile(ptr, size, ptr2, size2, ptr3, size3, ptr4, size4)
121 runtime.KeepAlive(fromFs)
122 runtime.KeepAlive(fromPath)
123 runtime.KeepAlive(toFs)
124 runtime.KeepAlive(toPath)
125 return ptrSizeToError(resPtrSize)
126}
127
128// ReplaceContent implements [model.ServerNeeded].
129func (s *pServer) ReplaceContent(fromFs model.FsBase, filepath string, oldContent string, content string) error {
130 s.atLeastOneChange = true
131 ptr, size := stringToPtr(string(fromFs))
132 ptr2, size2 := stringToPtr(filepath)
133 ptr3, size3 := stringToPtr(oldContent)
134 ptr4, size4 := stringToPtr(content)
135 resPtrSize := _replaceContent(ptr, size, ptr2, size2, ptr3, size3, ptr4, size4)
136 runtime.KeepAlive(fromFs)
137 runtime.KeepAlive(filepath)
138 runtime.KeepAlive(oldContent)
139 runtime.KeepAlive(content)
140 return ptrSizeToError(resPtrSize)
141}
142
143// WriteContent implements [model.ServerNeeded].
144func (s *pServer) WriteContent(fromFs model.FsBase, filepath string, content string) error {
145 s.atLeastOneChange = true
146 ptr, size := stringToPtr(string(fromFs))
147 ptr2, size2 := stringToPtr(filepath)
148 ptr3, size3 := stringToPtr(content)
149 resPtrSize := _writeContent(ptr, size, ptr2, size2, ptr3, size3)
150 runtime.KeepAlive(fromFs)
151 runtime.KeepAlive(filepath)
152 runtime.KeepAlive(content)
153 return ptrSizeToError(resPtrSize)
154}
155
156func (s *pServer) CommitAllIfNeeded(message string) {
157 if s.atLeastOneChange {
158 ptr, size := stringToPtr(message)
159 _commitAll(ptr, size)
160 runtime.KeepAlive(message)
161 }
162}
163
164func (s *pServer) DiffWithParent(hash string, oldFilepath string, newFilePath string) (string, error) {
165 ptrHash, sizeHash := stringToPtr(hash)
166 ptrOldFile, sizeOldFile := stringToPtr(oldFilepath)
167 ptrNewFile, sizeNewFile := stringToPtr(newFilePath)
168 ptrSizeDiff := _diffWithParent(ptrHash, sizeHash, ptrOldFile, sizeOldFile, ptrNewFile, sizeNewFile)
169 if ptrSizeDiff == 0 {
170 return "", errors.New("can't make diff")
171 }
172 ptrDiff := uint32(ptrSizeDiff >> 32)
173 sizeDiff := uint32(ptrSizeDiff)
174 return ptrToString(ptrDiff, sizeDiff), nil
175}
176
177func (s *pServer) Log(message string) {
178 ptr, size := stringToPtr(message)
179 _log(ptr, size)
180 runtime.KeepAlive(message)
181}
182
183func (s *pServer) LogError(message string, err error) {
184 ptr, size := stringToPtr(message)
185 errPtr, errSize := stringToPtr(err.Error())
186 _logError(ptr, size, errPtr, errSize)
187 runtime.KeepAlive(message)
188 runtime.KeepAlive(err)
189}
190
191func (s *pServer) Merge(from string, to string) {
192 fromPtr, fromSize := stringToPtr(from)
193 toPtr, toSize := stringToPtr(to)
194 _merge(fromPtr, fromSize, toPtr, toSize)
195 runtime.KeepAlive(from)
196 runtime.KeepAlive(to)
197}
198
199func (s *pServer) Commits(from string, to string) ([]model.Commit, error) {
200 fromPtr, fromSize := stringToPtr(from)
201 toPtr, toSize := stringToPtr(to)
202 ptrSizeDiff := _commits(fromPtr, fromSize, toPtr, toSize)
203 runtime.KeepAlive(from)
204 runtime.KeepAlive(to)
205 if ptrSizeDiff == 0 {
206 return nil, errors.New("can't get commits")
207 }
208 ptrDiff := uint32(ptrSizeDiff >> 32)
209 sizeDiff := uint32(ptrSizeDiff)
210 return model.CommitsFromString(ptrToString(ptrDiff, sizeDiff)), nil
211}
212
213func (s *pServer) Exec(exec model.Exec) (model.ExecStatus, error) {
214 execMarshalled, err := json.Marshal(exec)
215 if err != nil {
216 return model.ExecStatus{}, err
217 }
218 ptr, size := stringToPtr(string(execMarshalled))
219 ptrSize := _exec(ptr, size)
220 if ptrSize == 0 {
221 return model.ExecStatus{}, errors.New("can't exec")
222 }
223 ptrLog := uint32(ptrSize >> 32)
224 sizeLog := uint32(ptrSize)
225 return model.ExecStatusFromString(ptrToString(ptrLog, sizeLog)), nil
226}
227
228func (s *pServer) ReportToPlugin(report model.Report) {
229 if s.pluginOpts.Reporter != nil {
230 if err := s.pluginOpts.Reporter(report); err != nil {
231 s.LogError("plugin reporter err", err)
232 }
233 }
234}
235
236func (s *pServer) Report(level model.ReportLevel, content []string) error {
237 reportMarshalled, err := json.Marshal(model.ReportToGitroot{Level: level, Content: content})
238 if err != nil {
239 return err
240 }
241 ptr, size := stringToPtr(string(reportMarshalled))
242 _report(ptr, size)
243 return nil
244}
245
246func (s *pServer) ExportFunc(name string, callback func(args map[string]string) (map[string]string, error)) {
247 s.exportedFuncs[name] = callback
248}
249
250func (s *pServer) CanCallFunc(plugin string, name string, args map[string]string) bool {
251 call := model.Call{
252 Plugin: plugin,
253 Name: name,
254 Args: args,
255 }
256 callJson, err := json.Marshal(call)
257 if err != nil {
258 s.LogError("can't serialize canCall", err)
259 return false
260 }
261 ptr, size := stringToPtr(string(callJson))
262 ptrSize := _canCall(ptr, size)
263 if ptrSize == 0 {
264 return false
265 }
266 return true
267}
268
269func (s *pServer) CallFunc(plugin string, name string, args map[string]string) (map[string]string, error) {
270 call := model.Call{
271 Plugin: plugin,
272 Name: name,
273 Args: args,
274 }
275 callJson, err := json.Marshal(call)
276 if err != nil {
277 return nil, err
278 }
279 ptr, size := stringToPtr(string(callJson))
280 ptrSize := _call(ptr, size)
281 if ptrSize == 0 {
282 return nil, errors.New("can't call")
283 }
284 ptrRes := uint32(ptrSize >> 32)
285 sizeRes := uint32(ptrSize)
286 resCall := model.CallResFromString(ptrToString(ptrRes, sizeRes))
287 if resCall.Err != "" {
288 return nil, errors.New(resCall.Err)
289 }
290 return resCall.Res, nil
291}
292
293func (s *pServer) HttpClient(header map[string][]string) model.HttpClient {
294 return model.NewHttpClient(s, header)
295}
296
297func (s *pServer) DoRequest(request model.HttpRequest) (model.HttpResponse, error) {
298 requestJson, err := json.Marshal(request)
299 if err != nil {
300 return model.HttpResponse{}, err
301 }
302 ptr, size := stringToPtr(string(requestJson))
303 ptrSize := _httpClient(ptr, size)
304 if ptrSize == 0 {
305 return model.HttpResponse{}, errors.New("can't http")
306 }
307 ptrRes := uint32(ptrSize >> 32)
308 sizeRes := uint32(ptrSize)
309 res := model.HttpResponseFromString(ptrToString(ptrRes, sizeRes))
310 return res, nil
311}