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}