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.FS_BASE_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.FS_BASE_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.FS_BASE_CACHE, s)
83 }
84 return s.cache
85}
86
87// TODO delete after v0.4 is released
88// Deprecated: Use Worktree().WriteContent(filepath, content string) instead
89func (s *pServer) ModifyContent(filepath, content string) {
90 s.atLeastOneChange = true
91 ptr, size := stringToPtr(filepath)
92 ptr2, size2 := stringToPtr(content)
93 _modifyContent(ptr, size, ptr2, size2)
94 runtime.KeepAlive(filepath)
95 runtime.KeepAlive(content)
96}
97
98// TODO delete after v0.4 is released
99// Deprecated: Use Webcontent().WriteContent(filepath, content string) instead
100func (s *pServer) ModifyWebContent(filepath, content string) {
101 ptr, size := stringToPtr(filepath)
102 ptr2, size2 := stringToPtr(content)
103 _modifyWebContent(ptr, size, ptr2, size2)
104 runtime.KeepAlive(filepath)
105 runtime.KeepAlive(content)
106}
107
108// TODO delete after v0.4 is released
109// Deprecated: Use Webcontent().ReplaceContent(filepath string, oldContent string, content string) instead
110func (s *pServer) ReplaceWebContent(filepath, oldContent, content string) {
111 ptr, size := stringToPtr(filepath)
112 ptr2, size2 := stringToPtr(oldContent)
113 ptr3, size3 := stringToPtr(content)
114 _replaceWebContent(ptr, size, ptr2, size2, ptr3, size3)
115 runtime.KeepAlive(filepath)
116 runtime.KeepAlive(oldContent)
117 runtime.KeepAlive(content)
118}
119
120// TODO delete after v0.4 is released
121// Deprecated: Use Cache().WriteContent(filepath, content string) instead
122func (s *pServer) ModifyCacheContent(filepath, content string) {
123 ptr, size := stringToPtr(filepath)
124 ptr2, size2 := stringToPtr(content)
125 _modifyCacheContent(ptr, size, ptr2, size2)
126 runtime.KeepAlive(filepath)
127 runtime.KeepAlive(content)
128}
129
130// CopyFile implements [model.ServerNeeded].
131func (s *pServer) CopyFile(fromFs model.FsBase, fromPath string, toFs model.FsBase, toPath string) error {
132 ptr, size := stringToPtr(string(fromFs))
133 ptr2, size2 := stringToPtr(fromPath)
134 ptr3, size3 := stringToPtr(string(toFs))
135 ptr4, size4 := stringToPtr(toPath)
136 resPtrSize := _copyFile(ptr, size, ptr2, size2, ptr3, size3, ptr4, size4)
137 runtime.KeepAlive(fromFs)
138 runtime.KeepAlive(fromPath)
139 runtime.KeepAlive(toFs)
140 runtime.KeepAlive(toPath)
141 return ptrSizeToError(resPtrSize)
142}
143
144// DeleteFile implements [model.ServerNeeded].
145func (s *pServer) DeleteFile(fromFs model.FsBase, filename string) error {
146 ptr, size := stringToPtr(string(fromFs))
147 ptr2, size2 := stringToPtr(filename)
148 resPtrSize := _deleteFile(ptr, size, ptr2, size2)
149 runtime.KeepAlive(fromFs)
150 runtime.KeepAlive(filename)
151 return ptrSizeToError(resPtrSize)
152}
153
154// MoveFile implements [model.ServerNeeded].
155func (s *pServer) MoveFile(fromFs model.FsBase, fromPath string, toFs model.FsBase, toPath string) error {
156 ptr, size := stringToPtr(string(fromFs))
157 ptr2, size2 := stringToPtr(fromPath)
158 ptr3, size3 := stringToPtr(string(toFs))
159 ptr4, size4 := stringToPtr(toPath)
160 resPtrSize := _moveFile(ptr, size, ptr2, size2, ptr3, size3, ptr4, size4)
161 runtime.KeepAlive(fromFs)
162 runtime.KeepAlive(fromPath)
163 runtime.KeepAlive(toFs)
164 runtime.KeepAlive(toPath)
165 return ptrSizeToError(resPtrSize)
166}
167
168// ReplaceContent implements [model.ServerNeeded].
169func (s *pServer) ReplaceContent(fromFs model.FsBase, filepath string, oldContent string, content string) error {
170 ptr, size := stringToPtr(string(fromFs))
171 ptr2, size2 := stringToPtr(filepath)
172 ptr3, size3 := stringToPtr(oldContent)
173 ptr4, size4 := stringToPtr(content)
174 resPtrSize := _replaceContent(ptr, size, ptr2, size2, ptr3, size3, ptr4, size4)
175 runtime.KeepAlive(fromFs)
176 runtime.KeepAlive(filepath)
177 runtime.KeepAlive(oldContent)
178 runtime.KeepAlive(content)
179 return ptrSizeToError(resPtrSize)
180}
181
182// WriteContent implements [model.ServerNeeded].
183func (s *pServer) WriteContent(fromFs model.FsBase, filepath string, content string) error {
184 ptr, size := stringToPtr(string(fromFs))
185 ptr2, size2 := stringToPtr(filepath)
186 ptr3, size3 := stringToPtr(content)
187 resPtrSize := _writeContent(ptr, size, ptr2, size2, ptr3, size3)
188 runtime.KeepAlive(fromFs)
189 runtime.KeepAlive(filepath)
190 runtime.KeepAlive(content)
191 return ptrSizeToError(resPtrSize)
192}
193
194func (s *pServer) CommitAllIfNeeded(message string) {
195 if s.atLeastOneChange {
196 ptr, size := stringToPtr(message)
197 _commitAll(ptr, size)
198 runtime.KeepAlive(message)
199 }
200}
201
202func (s *pServer) DiffWithParent(hash string, oldFilepath string, newFilePath string) (string, error) {
203 ptrHash, sizeHash := stringToPtr(hash)
204 ptrOldFile, sizeOldFile := stringToPtr(oldFilepath)
205 ptrNewFile, sizeNewFile := stringToPtr(newFilePath)
206 ptrSizeDiff := _diffWithParent(ptrHash, sizeHash, ptrOldFile, sizeOldFile, ptrNewFile, sizeNewFile)
207 if ptrSizeDiff == 0 {
208 return "", errors.New("can't make diff")
209 }
210 ptrDiff := uint32(ptrSizeDiff >> 32)
211 sizeDiff := uint32(ptrSizeDiff)
212 return ptrToString(ptrDiff, sizeDiff), nil
213}
214
215func (s *pServer) Log(message string) {
216 ptr, size := stringToPtr(message)
217 _log(ptr, size)
218 runtime.KeepAlive(message)
219}
220
221func (s *pServer) LogError(message string, err error) {
222 ptr, size := stringToPtr(message)
223 errPtr, errSize := stringToPtr(err.Error())
224 _logError(ptr, size, errPtr, errSize)
225 runtime.KeepAlive(message)
226 runtime.KeepAlive(err)
227}
228
229func (s *pServer) Merge(from string, to string) {
230 fromPtr, fromSize := stringToPtr(from)
231 toPtr, toSize := stringToPtr(to)
232 _merge(fromPtr, fromSize, toPtr, toSize)
233 runtime.KeepAlive(from)
234 runtime.KeepAlive(to)
235}
236
237func (s *pServer) Commits(from string, to string) ([]model.Commit, error) {
238 fromPtr, fromSize := stringToPtr(from)
239 toPtr, toSize := stringToPtr(to)
240 ptrSizeDiff := _commits(fromPtr, fromSize, toPtr, toSize)
241 runtime.KeepAlive(from)
242 runtime.KeepAlive(to)
243 if ptrSizeDiff == 0 {
244 return nil, errors.New("can't get commits")
245 }
246 ptrDiff := uint32(ptrSizeDiff >> 32)
247 sizeDiff := uint32(ptrSizeDiff)
248 return model.CommitsFromString(ptrToString(ptrDiff, sizeDiff)), nil
249}
250
251func (s *pServer) Exec(exec model.Exec) (model.ExecStatus, error) {
252 execMarshalled, err := json.Marshal(exec)
253 if err != nil {
254 return model.ExecStatus{}, err
255 }
256 ptr, size := stringToPtr(string(execMarshalled))
257 ptrSize := _exec(ptr, size)
258 if ptrSize == 0 {
259 return model.ExecStatus{}, errors.New("can't exec")
260 }
261 ptrLog := uint32(ptrSize >> 32)
262 sizeLog := uint32(ptrSize)
263 return model.ExecStatusFromString(ptrToString(ptrLog, sizeLog)), nil
264}
265
266func (s *pServer) ReportToPlugin(report model.Report) {
267 if s.pluginOpts.Reporter != nil {
268 if err := s.pluginOpts.Reporter(report); err != nil {
269 s.LogError("plugin reporter err", err)
270 }
271 }
272}
273
274func (s *pServer) Report(level model.ReportLevel, content []string) error {
275 reportMarshalled, err := json.Marshal(model.ReportToGitroot{Level: level, Content: content})
276 if err != nil {
277 return err
278 }
279 ptr, size := stringToPtr(string(reportMarshalled))
280 _report(ptr, size)
281 return nil
282}
283
284func (s *pServer) ExportFunc(name string, callback func(args map[string]string) (map[string]string, error)) {
285 s.exportedFuncs[name] = callback
286}
287
288func (s *pServer) CanCallFunc(plugin string, name string, args map[string]string) bool {
289 call := model.Call{
290 Plugin: plugin,
291 Name: name,
292 Args: args,
293 }
294 callJson, err := json.Marshal(call)
295 if err != nil {
296 s.LogError("can't serialize canCall", err)
297 return false
298 }
299 ptr, size := stringToPtr(string(callJson))
300 ptrSize := _canCall(ptr, size)
301 if ptrSize == 0 {
302 return false
303 }
304 return true
305}
306
307func (s *pServer) CallFunc(plugin string, name string, args map[string]string) (map[string]string, error) {
308 call := model.Call{
309 Plugin: plugin,
310 Name: name,
311 Args: args,
312 }
313 callJson, err := json.Marshal(call)
314 if err != nil {
315 return nil, err
316 }
317 ptr, size := stringToPtr(string(callJson))
318 ptrSize := _call(ptr, size)
319 if ptrSize == 0 {
320 return nil, errors.New("can't call")
321 }
322 ptrRes := uint32(ptrSize >> 32)
323 sizeRes := uint32(ptrSize)
324 resCall := model.CallResFromString(ptrToString(ptrRes, sizeRes))
325 if resCall.Err != "" {
326 return nil, errors.New(resCall.Err)
327 }
328 return resCall.Res, nil
329}