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 "slices"
10 "time"
11
12 "github.com/samber/oops"
13 "github.com/tetratelabs/wazero/api"
14 pluginLib "gitroot.dev/libs/golang/plugin/model"
15 "gitroot.dev/server/logger"
16 "gitroot.dev/server/repository"
17)
18
19type callPlugin struct {
20 manager *Manager
21 plugin Plugin
22 repo *repository.GitRootRepository
23 repoWriter *repository.GitRootRepositoryWrite
24 module api.Module
25 logger *logger.Logger
26}
27
28func (c callPlugin) callPluginForDiff(ctx context.Context, r *runtime, commands []CommandForDiff) error {
29 startCommit := c.module.ExportedFunction("startCommit")
30 addFile := c.module.ExportedFunction("addFile")
31 modFile := c.module.ExportedFunction("modFile")
32 delFile := c.module.ExportedFunction("delFile")
33 endCommit := c.module.ExportedFunction("endCommit")
34 malloc := c.module.ExportedFunction("gitrootAlloc")
35 if malloc == nil {
36 malloc = c.module.ExportedFunction("malloc")
37 }
38 free := c.module.ExportedFunction("gitrootFree")
39 if free == nil {
40 free = c.module.ExportedFunction("free")
41 }
42
43 currentBranch, err := c.repo.CurrentBranch()
44 if err != nil {
45 return oops.Wrapf(err, "can't get CurrentBranch")
46 }
47
48 for _, cmd := range commands {
49 r.command = &cmd
50 for _, pluginRun := range r.plugin.Run {
51 r.pluginRun = pluginRun
52 callOnAdd := addFile != nil && slices.Contains(pluginRun.When, pluginLib.PluginRunWhenAdd)
53 callOnMod := modFile != nil && slices.Contains(pluginRun.When, pluginLib.PluginRunWhenMod)
54 callOnDel := delFile != nil && slices.Contains(pluginRun.When, pluginLib.PluginRunWhenDel)
55 isAuthorized := checkBranch(pluginRun, cmd.branch)
56 if !isAuthorized {
57 continue
58 }
59
60 if cmd.branchAction == commitForDiffActionDel {
61 c.logger.Info("delete branch", logger.NewLoggerPair("branch", cmd.branch))
62 continue
63 }
64
65 if err := c.repoWriter.Checkout(cmd.branch); err != nil {
66 c.logger.Error("can't Checkout from cmd", err, logger.NewLoggerPair("branch", cmd.branch))
67 continue
68 }
69
70 if init := c.module.ExportedFunction("init"); init != nil {
71 arg, err := pluginRun.Marshal()
72 if err != nil {
73 c.logger.Error("can't Marshal pluginRun", err, logger.NewLoggerPair("branch", cmd.branch))
74 continue
75 }
76 c.logger.Debug("init plugin", logger.NewLoggerPair("name", c.plugin.Name), logger.NewLoggerPair("arg", arg))
77 if err := r.writeMemoryAndCall(c.module, init, malloc, free, c.repo.Name(), "false", string(arg)); err != nil {
78 c.logger.Error("can't init plugin", err, logger.NewLoggerPair("branch", cmd.branch), logger.NewLoggerPair("plugin", c.plugin.Name))
79 continue
80 }
81 }
82
83 for _, com := range cmd.commits {
84 comMarshalled, err := MarshallOne(cmd.branch.Short(), com)
85 if err != nil {
86 c.logger.Error("can't marshall commit", err, logger.NewLoggerPair("branch", cmd.branch))
87 continue
88 }
89 if startCommit != nil {
90 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)))
91 if err := r.writeMemoryAndCall(c.module, startCommit, malloc, free, comMarshalled); err != nil {
92 c.logger.Error("startCommit error", err, logger.NewLoggerPair("branch", cmd.branch), logger.NewLoggerPair("plugin", c.plugin.Name))
93 continue
94 }
95 }
96
97 for _, f := range com.files {
98 if f.action == commitForDiffActionAdd && callOnAdd {
99 c.logger.Debug("add", logger.NewLoggerPair("confPath", pluginRun.Path), logger.NewLoggerPair("currentPath", f.path))
100 if pluginRun.glob.Match(f.path) {
101 // creation
102 r.writeMemoryAndCall(c.module, addFile, malloc, free, f.path)
103 }
104 } else {
105 if pluginRun.glob.Match(f.path) {
106 if f.action == commitForDiffActionDel && callOnDel {
107 // deletion
108 r.writeMemoryAndCall(c.module, delFile, malloc, free, f.path)
109 } else if f.action == commitForDiffActionMod && callOnMod {
110 //modification
111 r.writeMemoryAndCall(c.module, modFile, malloc, free, f.oldPath, f.path)
112 }
113 }
114 }
115 }
116
117 if endCommit != nil {
118 if err := r.writeMemoryAndCall(c.module, endCommit, malloc, free, comMarshalled); err != nil {
119 c.logger.Error("endCommit error", err, logger.NewLoggerPair("branch", cmd.branch), logger.NewLoggerPair("plugin", c.plugin.Name))
120 continue
121 }
122 }
123 }
124
125 if finish := c.module.ExportedFunction("finish"); finish != nil {
126 if _, err := finish.Call(ctx); err != nil {
127 c.logger.Error("finish error", err, logger.NewLoggerPair("branch", cmd.branch), logger.NewLoggerPair("plugin", c.plugin.Name))
128 continue
129 }
130 }
131 }
132 }
133
134 if err := c.repoWriter.Checkout(currentBranch); err != nil {
135 c.logger.Error("can't Checkout currentBranch", err, logger.NewLoggerPair("branch", currentBranch.Short()))
136 return err
137 }
138 return nil
139}
140
141func (r *runtime) writeMemoryAndCall(module api.Module, toCall api.Function, malloc api.Function, free api.Function, message ...string) error {
142 params := make([]uint64, 0)
143 for _, m := range message {
144 size := uint64(len(m))
145
146 results, err := malloc.Call(r.ctx, size)
147 if err != nil {
148 return oops.Wrapf(err, "can't malloc memory for %s with %s", toCall.Definition().Name(), m)
149 }
150 ptr := results[0]
151
152 // The pointer is a linear memory offset, which is where we write the name.
153 if !module.Memory().Write(uint32(ptr), []byte(m)) {
154 return oops.Wrapf(err, "can't write memory")
155 }
156
157 params = append(params, ptr, size)
158 }
159
160 defer func() {
161 for i, d := range params {
162 if i%2 == 0 {
163 if free != nil && len(free.Definition().ParamTypes()) == 1 {
164 _, err := free.Call(r.ctx, d)
165 if err != nil {
166 r.logger.Error("can't free writeMemoryAndCall", err)
167 }
168 } else if free != nil {
169 _, err := free.Call(r.ctx, d, params[i+1])
170 if err != nil {
171 r.logger.Error("can't free(ptr, size) writeMemoryAndCall", err, logger.NewLoggerPair("nbParam", len(free.Definition().ParamTypes())))
172 }
173 }
174 }
175
176 }
177 }()
178
179 if _, err := toCall.Call(r.ctx, params...); err != nil {
180 return oops.With("method", toCall.Definition().ExportNames()).Wrapf(err, "can't call")
181 }
182
183 return nil
184}