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
5package plugin
6
7import (
8 "context"
9 "fmt"
10 "io/fs"
11 "slices"
12 "time"
13
14 "github.com/go-git/go-git/v5/plumbing"
15 "github.com/go-git/go-git/v5/plumbing/protocol/packp"
16 "github.com/samber/oops"
17 "github.com/tetratelabs/wazero/api"
18 pluginLib "gitroot.dev/libs/golang/plugin/model"
19 grfs "gitroot.dev/server/fs"
20 "gitroot.dev/server/logger"
21 "gitroot.dev/server/repository"
22 "gitroot.dev/server/user"
23)
24
25type callPlugin struct {
26 manager *Manager
27 plugin Plugin
28 repo *repository.GitRootRepository
29 repoWriter *repository.GitRootRepositoryWrite
30 module api.Module
31 logger *logger.Logger
32}
33
34func (r *runtime) start(ctx context.Context, repo *repository.GitRootRepository, repoWriter *repository.GitRootRepositoryWrite, plugins []Plugin, commands []CommandForDiff, mergeHook func(cmd *packp.Command, pusher user.SimpleUser, toDeleteBranchName string)) error {
35 r.repo = repo
36 r.repoWriter = repoWriter
37 r.command = nil
38 r.commit = nil
39
40 r.commitHook = func(hash plumbing.Hash) {
41 command, err := repoWriter.GetLastCommit(hash)
42 if err != nil {
43 r.logger.Error("can't GetLastCommit in start runtime", err)
44 return
45 }
46 newCmd, err := CommandForDiffFromCommitCmd(ctx, r.plugin.commiter.SimpleUser, command, r.command.branch)
47 if err != nil {
48 r.logger.Error("can't CommandForDiffFromCommitCmd in start runtime", err)
49 return
50 }
51 commands = append(commands, newCmd)
52 }
53
54 r.mergeHook = mergeHook
55 defer func() {
56 r.commitHook = nil
57 r.mergeHook = nil
58 }()
59
60 fs := grfs.NewMultiple(ctx, map[string]fs.FS{
61 "worktree": r.repoWriter.ToFs(ctx),
62 "webcontent": r.manager.conf.DataWeb(r.repo.Name()),
63 })
64
65 for _, plugin := range plugins {
66 fs.UpdateSubFs("cache", r.manager.conf.Cache(r.repo.Name(), plugin.Name))
67 r.plugin = plugin
68 timerStop := r.logger.Time(fmt.Sprintf("Timer %s", plugin.Name))
69 r.logger.Debug("start plugin", logger.NewLoggerPair("repo", repo.Name()), logger.NewLoggerPair("name", plugin.Name))
70 m, err := r.loadModule(ctx, plugin, fs)
71 if err != nil {
72 r.logger.Error("loadModule for start error", err, logger.NewLoggerPair("name", plugin.Name))
73 continue
74 }
75 l := logger.NewLogger(logger.WASM)
76 l.Debug("memory before", logger.NewLoggerPair("size", m.Memory().Size()), logger.NewLoggerPair("plugin", plugin.Name))
77 cp := callPlugin{
78 manager: r.manager,
79 plugin: plugin,
80 repo: repo,
81 repoWriter: repoWriter,
82 module: m,
83 logger: r.logger.NewSubLogger(plugin.Name),
84 }
85 if err := cp.callPluginForDiff(ctx, r, commands); err != nil {
86 r.logger.Error("finish plugin with error", err, logger.NewLoggerPair("name", plugin.Name))
87 }
88 l.Debug("memory after", logger.NewLoggerPair("size", m.Memory().Size()), logger.NewLoggerPair("plugin", plugin.Name))
89 r.logger.Debug("finish plugin", logger.NewLoggerPair("name", plugin.Name))
90 r.garbageMemory(m)
91 timerStop()
92 }
93 return nil
94}
95
96func (c callPlugin) callPluginForDiff(ctx context.Context, r *runtime, commands []CommandForDiff) error {
97 startCommit := c.module.ExportedFunction("startCommit")
98 addFile := c.module.ExportedFunction("addFile")
99 modFile := c.module.ExportedFunction("modFile")
100 delFile := c.module.ExportedFunction("delFile")
101 endCommit := c.module.ExportedFunction("endCommit")
102 malloc := c.module.ExportedFunction("gitrootAlloc")
103 if malloc == nil {
104 malloc = c.module.ExportedFunction("malloc")
105 }
106
107 currentBranch, err := c.repo.CurrentBranch()
108 if err != nil {
109 return oops.Wrapf(err, "can't get CurrentBranch")
110 }
111
112 for _, cmd := range commands {
113 r.command = &cmd
114 for _, pluginRun := range r.plugin.Run {
115 r.pluginRun = pluginRun
116 callOnAdd := addFile != nil && slices.Contains(pluginRun.When, pluginLib.PluginRunWhenAdd)
117 callOnMod := modFile != nil && slices.Contains(pluginRun.When, pluginLib.PluginRunWhenMod)
118 callOnDel := delFile != nil && slices.Contains(pluginRun.When, pluginLib.PluginRunWhenDel)
119 isAuthorized := checkBranch(pluginRun, cmd.branch)
120 if !isAuthorized {
121 continue
122 }
123
124 if cmd.branchAction == commitForDiffActionDel {
125 c.logger.Info("delete branch", logger.NewLoggerPair("branch", cmd.branch))
126 continue
127 }
128
129 if err := c.repoWriter.Checkout(cmd.branch); err != nil {
130 c.logger.Error("can't Checkout from cmd", err, logger.NewLoggerPair("branch", cmd.branch))
131 continue
132 }
133
134 if init := c.module.ExportedFunction("init"); init != nil {
135 arg, err := pluginRun.Marshal()
136 if err != nil {
137 c.logger.Error("can't Marshal pluginRun", err, logger.NewLoggerPair("branch", cmd.branch))
138 continue
139 }
140 c.logger.Debug("init plugin", logger.NewLoggerPair("name", c.plugin.Name), logger.NewLoggerPair("arg", arg))
141 if err := r.writeMemoryAndCall(c.module, init, malloc, c.repo.Name(), "false", string(arg)); err != nil {
142 c.logger.Error("can't init plugin", err, logger.NewLoggerPair("branch", cmd.branch), logger.NewLoggerPair("plugin", c.plugin.Name))
143 continue
144 }
145 }
146
147 for _, com := range cmd.commits {
148 r.commit = &com
149 comMarshalled, err := MarshallOne(cmd.branch.Short(), com)
150 if err != nil {
151 c.logger.Error("can't marshall commit", err, logger.NewLoggerPair("branch", cmd.branch))
152 continue
153 }
154 if startCommit != nil {
155 c.logger.Debug("startCommit", logger.NewLoggerPair("branch", cmd.branch.Short()), logger.NewLoggerPair("hash", com.hash.String()), logger.NewLoggerPair("message", com.message), logger.NewLoggerPair("date", com.date.Format(time.RFC3339)))
156 if err := r.writeMemoryAndCall(c.module, startCommit, malloc, comMarshalled); err != nil {
157 c.logger.Error("startCommit error", err, logger.NewLoggerPair("branch", cmd.branch), logger.NewLoggerPair("plugin", c.plugin.Name))
158 continue
159 }
160 }
161
162 for _, f := range com.files {
163 if f.action == commitForDiffActionAdd && callOnAdd {
164 c.logger.Debug("add", logger.NewLoggerPair("confPath", pluginRun.Path), logger.NewLoggerPair("currentPath", f.path))
165 if pluginRun.glob.Match(f.path) {
166 // creation
167 r.writeMemoryAndCall(c.module, addFile, malloc, f.path)
168 }
169 } else {
170 if pluginRun.glob.Match(f.path) {
171 if f.action == commitForDiffActionDel && callOnDel {
172 // deletion
173 r.writeMemoryAndCall(c.module, delFile, malloc, f.path)
174 } else if f.action == commitForDiffActionMod && callOnMod {
175 //modification
176 r.writeMemoryAndCall(c.module, modFile, malloc, f.oldPath, f.path)
177 }
178 }
179 }
180 }
181
182 if endCommit != nil {
183 if err := r.writeMemoryAndCall(c.module, endCommit, malloc, comMarshalled); err != nil {
184 c.logger.Error("endCommit error", err, logger.NewLoggerPair("branch", cmd.branch), logger.NewLoggerPair("plugin", c.plugin.Name))
185 continue
186 }
187 }
188 }
189
190 if finish := c.module.ExportedFunction("finish"); finish != nil {
191 if _, err := finish.Call(ctx); err != nil {
192 c.logger.Error("finish error", err, logger.NewLoggerPair("branch", cmd.branch), logger.NewLoggerPair("plugin", c.plugin.Name))
193 continue
194 }
195 }
196 }
197 }
198
199 if err := c.repoWriter.Checkout(currentBranch); err != nil {
200 c.logger.Error("can't Checkout currentBranch", err, logger.NewLoggerPair("branch", currentBranch.Short()))
201 return err
202 }
203 return nil
204}
205
206func (r *runtime) writeMemoryAndCall(module api.Module, toCall api.Function, malloc api.Function, message ...string) error {
207 params := make([]uint64, 0)
208 for _, m := range message {
209 size := uint64(len(m))
210
211 results, err := malloc.Call(r.ctx, size)
212 if err != nil {
213 return oops.Wrapf(err, "can't malloc memory for %s with %s", toCall.Definition().Name(), m)
214 }
215 ptr := results[0]
216
217 // The pointer is a linear memory offset, which is where we write the name.
218 if !module.Memory().Write(uint32(ptr), []byte(m)) {
219 return oops.Wrapf(err, "can't write memory")
220 }
221
222 params = append(params, ptr, size)
223 }
224
225 defer func() {
226 ptrSizes := make([]ptrSize, 0)
227 for i, d := range params {
228 if i%2 == 0 {
229 ptrSizes = append(ptrSizes, ptrSize{ptr: d, size: params[i+1]})
230 }
231 }
232 r.free(module, ptrSizes)
233 }()
234
235 if _, err := toCall.Call(r.ctx, params...); err != nil {
236 return oops.With("method", toCall.Definition().ExportNames()).Wrapf(err, "can't call")
237 }
238
239 return nil
240}