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 "bytes"
9 "context"
10 "crypto/rand"
11 "encoding/json"
12 "errors"
13 "fmt"
14 "io/fs"
15 "os"
16 "path/filepath"
17 "unicode/utf16"
18
19 "github.com/go-git/go-git/v5/plumbing"
20 "github.com/go-git/go-git/v5/plumbing/object"
21 "github.com/go-git/go-git/v5/plumbing/protocol/packp"
22 "github.com/samber/oops"
23 "github.com/tetratelabs/wazero"
24 "github.com/tetratelabs/wazero/api"
25 "github.com/tetratelabs/wazero/imports/assemblyscript"
26 "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
27 "github.com/tetratelabs/wazero/sys"
28 pluginLib "gitroot.dev/libs/golang/plugin/model"
29 "gitroot.dev/server/logger"
30 "gitroot.dev/server/repository"
31 "gitroot.dev/server/user"
32)
33
34type runtimeInputsKind int
35
36const (
37 runtimeInputsKindDiff runtimeInputsKind = iota
38 runtimeInputsKindWorktree runtimeInputsKind = iota
39)
40
41type runtimeInputs struct {
42 ctx context.Context
43 repoName string
44 kind runtimeInputsKind
45 plugins []Plugin
46 commands []CommandForDiff
47}
48
49type runtime struct {
50 ctx context.Context
51 manager *Manager
52 logger *logger.Logger
53 repo *repository.GitRootRepository
54 repoWriter *repository.GitRootRepositoryWrite
55 plugin Plugin
56 pluginRun PluginRun
57 command *CommandForDiff
58 commit *commitForDiffCommit
59 wazRun wazero.Runtime
60 commitHook func(h plumbing.Hash)
61 mergeHook func(cmd *packp.Command, pusher user.SimpleUser, toDeleteBranchName string)
62 reports []pluginLib.Report
63 memoryToGarbage []ptrSize
64 modulesToGarbage []api.Module
65}
66
67func newRuntime(ctx context.Context, manager *Manager, logger *logger.Logger, runtimeConfig wazero.RuntimeConfig) (*runtime, error) {
68 r := &runtime{
69 ctx: ctx,
70 manager: manager,
71 logger: logger,
72 wazRun: wazero.NewRuntimeWithConfig(ctx, runtimeConfig),
73 commitHook: nil,
74 mergeHook: nil,
75 memoryToGarbage: []ptrSize{},
76 modulesToGarbage: []api.Module{},
77 }
78
79 _, err := assemblyscript.Instantiate(ctx, r.wazRun)
80 if err != nil {
81 return nil, err
82 }
83
84 _, err = r.wazRun.
85 NewHostModuleBuilder("gitroot").
86 NewFunctionBuilder().WithFunc(r.ForgeConf).Export("forgeConf").
87 NewFunctionBuilder().WithFunc(r.ModifyContentBuilder(false)).Export("modifyContent").
88 NewFunctionBuilder().WithFunc(r.ModifyContentBuilder(true)).Export("modifyContentAS").
89 NewFunctionBuilder().WithFunc(r.ModifyWebContentBuilder(false)).Export("modifyWebContent").
90 NewFunctionBuilder().WithFunc(r.ModifyWebContentBuilder(true)).Export("modifyWebContentAS").
91 NewFunctionBuilder().WithFunc(r.ReplaceWebContentBuilder(false)).Export("replaceWebContent").
92 NewFunctionBuilder().WithFunc(r.ReplaceWebContentBuilder(true)).Export("replaceWebContentAS").
93 NewFunctionBuilder().WithFunc(r.ModifyCacheContentBuilder(false)).Export("modifyCacheContent").
94 NewFunctionBuilder().WithFunc(r.ModifyCacheContentBuilder(true)).Export("modifyCacheContentAS").
95 NewFunctionBuilder().WithFunc(r.CommitAllBuilder(false)).Export("commitAll").
96 NewFunctionBuilder().WithFunc(r.CommitAllBuilder(true)).Export("commitAllAS").
97 NewFunctionBuilder().WithFunc(r.DiffWithParentBuilder(false)).Export("diffWithParent").
98 NewFunctionBuilder().WithFunc(r.DiffWithParentBuilder(true)).Export("diffWithParentAS").
99 NewFunctionBuilder().WithFunc(r.LogBuilder(false)).Export("log").
100 NewFunctionBuilder().WithFunc(r.LogBuilder(true)).Export("logAS").
101 NewFunctionBuilder().WithFunc(r.LogErrorBuilder(false)).Export("logError").
102 NewFunctionBuilder().WithFunc(r.LogErrorBuilder(true)).Export("logErrorAS").
103 NewFunctionBuilder().WithFunc(r.MergeBuilder(false)).Export("merge").
104 NewFunctionBuilder().WithFunc(r.MergeBuilder(true)).Export("mergeAS").
105 NewFunctionBuilder().WithFunc(r.GetCommitsBuilder(false)).Export("commits").
106 NewFunctionBuilder().WithFunc(r.GetCommitsBuilder(true)).Export("commitsAS").
107 NewFunctionBuilder().WithFunc(r.ExecBuilder(false)).Export("exec").
108 NewFunctionBuilder().WithFunc(r.ExecBuilder(true)).Export("execAS").
109 NewFunctionBuilder().WithFunc(r.ReportBuilder(false)).Export("report").
110 NewFunctionBuilder().WithFunc(r.ReportBuilder(true)).Export("reportAS").
111 Instantiate(ctx)
112 if err != nil {
113 return nil, err
114 }
115 _, err = wasi_snapshot_preview1.Instantiate(ctx, r.wazRun)
116 if err != nil {
117 return nil, err
118 }
119 return r, nil
120}
121
122type hasBeenMerged struct {
123 cmd *packp.Command
124 pusher user.SimpleUser
125 toDeleteBranchName string
126}
127
128func (r *runtime) listen(c chan runtimeInputs) {
129 postMergeActions := []hasBeenMerged{}
130 for i := range c {
131 r.logger.Debug("listen call", logger.NewLoggerPair("repo", i.repoName), logger.NewLoggerPair("isDiff", i.kind == runtimeInputsKindDiff), logger.NewLoggerPair("isWorktree", i.kind == runtimeInputsKindWorktree))
132 repo, err := r.manager.repoManager.Open(logger.AddCaller(r.ctx, "runtime.listen"), i.repoName)
133 if err != nil {
134 r.logger.Error("open error in listen", err)
135 repo.Close()
136 continue
137 }
138 repoWriter, err := repo.WillWrite(plumbing.HEAD) //TODO should mount good branch directly inside i.commands
139 if err != nil {
140 r.logger.Error("will write error in listen", err, logger.NewLoggerPair("repo", repo.Name()))
141 repo.Close()
142 continue
143 }
144
145 switch i.kind {
146 case runtimeInputsKindDiff:
147 timerStop := r.logger.Time(fmt.Sprintf("Timer %s all plugins", repo.Name()))
148 err := r.start(i.ctx, repo, repoWriter, i.plugins, i.commands, func(cmd *packp.Command, pusher user.SimpleUser, toDeleteBranchName string) {
149 if cmd != nil {
150 postMergeActions = append(postMergeActions, hasBeenMerged{
151 cmd: cmd,
152 pusher: pusher,
153 toDeleteBranchName: toDeleteBranchName,
154 })
155 }
156 })
157 if err != nil {
158 r.logger.Error("start error", err)
159 }
160 timerStop()
161 case runtimeInputsKindWorktree:
162 r.logger.Info("start worktree", logger.NewLoggerPair("repo", repo.Name()))
163 timerStop := r.logger.Time(fmt.Sprintf("Timer %s all plugins worktree", repo.Name()))
164 if err := r.worktree(i.ctx, repo, repoWriter, i.plugins, i.commands); err != nil {
165 r.logger.Error("start error", err)
166 }
167 timerStop()
168 }
169 r.sendReports(i.ctx, repo, repoWriter, i.plugins)
170 r.garbage()
171 repo.Close()
172 for _, postMerge := range postMergeActions {
173 r.manager.backgroundManager.DeleteBranch(i.repoName, postMerge.toDeleteBranchName)
174 r.manager.backgroundManager.PostPush(postMerge.pusher, i.repoName, []*packp.Command{postMerge.cmd})
175 }
176 postMergeActions = nil
177 }
178}
179
180func (r *runtime) loadModule(ctx context.Context, plugin Plugin, withFs fs.FS) (api.Module, error) {
181 m := r.wazRun.Module(plugin.uuid())
182 if m == nil {
183 r.logger.Debug("instantiate plugin conf", logger.NewLoggerPair("name", plugin.Name), logger.NewLoggerPair("with external fs", withFs != nil))
184 config := wazero.NewModuleConfig().
185 WithStdout(os.Stdout).WithStderr(os.Stderr).
186 WithSysWalltime().
187 WithSysNanotime().
188 WithRandSource(rand.Reader).
189 WithName(plugin.uuid()).
190 WithStartFunctions("_initialize", "install")
191
192 if withFs != nil {
193 config = config.WithFS(withFs)
194 } else {
195 r.logger.Warn("no fs mounted", logger.NewLoggerPair("plugin", plugin.Name), logger.NewLoggerPair("repo is nil", r.repo == nil), logger.NewLoggerPair("repoWrite is nil", r.repoWriter == nil))
196 }
197
198 mod, err := r.wazRun.InstantiateModule(ctx, plugin.compiledModule, config)
199 if err != nil {
200 if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
201 fmt.Fprintf(os.Stderr, "exit_code: %d\n", exitErr.ExitCode())
202 return nil, err
203 } else if !ok {
204 return nil, err
205 }
206 }
207 m = mod
208 } else {
209 r.logger.Debug("already exist conf", logger.NewLoggerPair("name", plugin.Name))
210 }
211 r.modulesToGarbage = append(r.modulesToGarbage, m)
212 return m, nil
213}
214
215func (r *runtime) garbageMemory(m api.Module) {
216 r.free(m, r.memoryToGarbage)
217 r.memoryToGarbage = []ptrSize{}
218}
219
220func (r *runtime) garbage() {
221 r.reports = []pluginLib.Report{}
222 for _, m := range r.modulesToGarbage {
223 if err := m.Close(r.ctx); err != nil {
224 r.logger.Error("close module fail", err, logger.NewLoggerPair("name", m.Name()))
225 }
226 }
227 r.modulesToGarbage = []api.Module{}
228}
229
230func (r *runtime) ForgeConf(_ context.Context, m api.Module) uint64 {
231 forgeConf, err := json.Marshal(r.manager.conf.ForgeConf())
232 if err != nil {
233 r.logger.Error("ForgeConf can't be serialized", err)
234 return 0
235 }
236 ptrSize, err := r.sendData(m, string(forgeConf))
237 if err != nil {
238 r.logger.Error("ForgeConf can't send data", err)
239 return 0
240 }
241 return ptrSize
242}
243
244func (r *runtime) ModifyContentBuilder(forAS bool) interface{} {
245 f := func(filename string, content string) {
246 if ok, err := checkWrite(r.pluginRun.write.git, r.repoWriter.ToFs(r.ctx), filename); !ok {
247 r.logger.Error("plugin can't write in git", err, logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename))
248 return
249 }
250 r.logger.Debug("modifyContent", logger.NewLoggerPair("file", filename))
251 if err := r.repoWriter.Write(filename, []byte(content)); err != nil {
252 r.logger.Error("modifyContent can't open file", err, logger.NewLoggerPair("filepath", filename))
253 return
254 }
255 }
256
257 if !forAS {
258 return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) {
259 filename, err := r.readString(m, "ModifyContent filename", filenamePtr, filenameSize)
260 if err != nil {
261 return
262 }
263 content, err := r.readString(m, "ModifyContent content", contentPtr, contentSize)
264 if err != nil {
265 return
266 }
267 f(filename, content)
268 }
269 } else {
270 return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) {
271 filename, err := r.readASString(m, "ModifyContent filename", filenamePtr)
272 if err != nil {
273 return
274 }
275 content, err := r.readASString(m, "ModifyContent content", contentPtr)
276 if err != nil {
277 return
278 }
279 f(filename, content)
280 }
281 }
282}
283
284func (r *runtime) ModifyWebContentBuilder(forAS bool) interface{} {
285 f := func(filename string, content string) {
286 if r.repo == nil { //in forconf scenario their is no repo
287 r.logger.Error("ModifyWebContentBuilder can't in forconf", errors.New("no repo"), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("filepath", filename))
288 return
289 }
290 if ok, err := checkWrite(r.pluginRun.write.web, r.manager.conf.DataWeb(r.repo.Name()), filename); !ok {
291 r.logger.Warn("plugin can't write in web", logger.NewLoggerPair("err", err), logger.NewLoggerPair("repo", r.repo.Name()), logger.NewLoggerPair("branch", r.command.branch), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename))
292 return
293 }
294 fullPath := r.repo.PathDataWeb(filename)
295 dir, _ := filepath.Split(fullPath)
296 if err := os.MkdirAll(dir, os.ModePerm); err != nil {
297 r.logger.Error("modifyWebContent can't mkdirAll", err, logger.NewLoggerPair("filepath", filename))
298 return
299 }
300 if err := os.WriteFile(fullPath, []byte(content), 0666); err != nil {
301 r.logger.Error("modifyWebContent can't open file", err, logger.NewLoggerPair("filepath", filename))
302 return
303 }
304 r.logger.Debug("Write in web", logger.NewLoggerPair("fullPath", fullPath))
305 }
306
307 if !forAS {
308 return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) {
309 filename, err := r.readString(m, "ModifyWebContent filename", filenamePtr, filenameSize)
310 if err != nil {
311 return
312 }
313 content, err := r.readString(m, "ModifyWebContent content", contentPtr, contentSize)
314 if err != nil {
315 return
316 }
317 f(filename, content)
318 }
319 } else {
320 return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) {
321 filename, err := r.readASString(m, "ModifyWebContent filename", filenamePtr)
322 if err != nil {
323 return
324 }
325 content, err := r.readASString(m, "ModifyWebContent content", contentPtr)
326 if err != nil {
327 return
328 }
329 f(filename, content)
330 }
331 }
332}
333
334func (r *runtime) ReplaceWebContentBuilder(forAS bool) interface{} {
335 f := func(filename string, oldContent string, content string) {
336 if r.repo == nil { //in forconf scenario their is no repo
337 r.logger.Error("ReplaceWebContentBuilder can't in forconf", errors.New("no repo"), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("filepath", filename))
338 return
339 }
340 if ok, err := checkWrite(r.pluginRun.write.web, r.manager.conf.DataWeb(r.repo.Name()), filename); !ok {
341 r.logger.Warn("plugin can't write in web for replace", logger.NewLoggerPair("err", err), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename))
342 return
343 }
344 fullPath := r.repo.PathDataWeb(filename)
345 fileContent, err := os.ReadFile(fullPath)
346 if err != nil {
347 r.logger.Error("ReplaceWebContentBuilder can't ReadFile", err, logger.NewLoggerPair("filepath", filename))
348 return
349 }
350 if err := os.WriteFile(fullPath, bytes.Replace(fileContent, []byte(oldContent), []byte(content), 1), 0666); err != nil {
351 r.logger.Error("ReplaceWebContentBuilder can't open file", err, logger.NewLoggerPair("filepath", filename))
352 return
353 }
354 r.logger.Debug("Write in web", logger.NewLoggerPair("fullPath", fullPath))
355 }
356
357 if !forAS {
358 return func(_ context.Context, m api.Module, filenamePtr, filenameSize, oldContentPtr, oldContentSize, contentPtr, contentSize uint32) {
359 filename, err := r.readString(m, "ReplaceWebContentBuilder filename", filenamePtr, filenameSize)
360 if err != nil {
361 return
362 }
363 oldContent, err := r.readString(m, "ReplaceWebContentBuilder oldContent", oldContentPtr, oldContentSize)
364 if err != nil {
365 return
366 }
367 content, err := r.readString(m, "ReplaceWebContentBuilder content", contentPtr, contentSize)
368 if err != nil {
369 return
370 }
371 f(filename, oldContent, content)
372 }
373 } else {
374 return func(_ context.Context, m api.Module, filenamePtr, oldContentPtr, contentPtr uint32) {
375 filename, err := r.readASString(m, "ReplaceWebContentBuilder filename", filenamePtr)
376 if err != nil {
377 return
378 }
379 oldContent, err := r.readASString(m, "ReplaceWebContentBuilder oldContent", oldContentPtr)
380 if err != nil {
381 return
382 }
383 content, err := r.readASString(m, "ReplaceWebContentBuilder content", contentPtr)
384 if err != nil {
385 return
386 }
387 f(filename, oldContent, content)
388 }
389 }
390}
391
392func (r *runtime) ModifyCacheContentBuilder(forAS bool) interface{} {
393 f := func(filename string, content string) {
394 r.logger.Debug("ModifyCacheContent", logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("file", filename))
395 if r.repo == nil { //in forconf scenario their is no repo
396 r.logger.Error("ModifyCacheContent can't in forconf", errors.New("no repo"), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("filepath", filename))
397 return
398 }
399 fullPath := filepath.Join(r.manager.conf.PathCache(), r.repo.Name(), r.plugin.Name, filename)
400 dir, _ := filepath.Split(fullPath)
401 if err := os.MkdirAll(dir, os.ModePerm); err != nil {
402 r.logger.Error("ModifyCacheContent can't mkdirAll", err, logger.NewLoggerPair("filepath", filename))
403 return
404 }
405 if err := os.WriteFile(fullPath, []byte(content), 0666); err != nil {
406 r.logger.Error("ModifyCacheContent can't open file", err, logger.NewLoggerPair("filepath", filename))
407 return
408 }
409 r.logger.Debug("Write in cache", logger.NewLoggerPair("fullPath", fullPath))
410 }
411 if !forAS {
412 return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) {
413 filename, err := r.readString(m, "ModifyCacheContent filename", filenamePtr, filenameSize)
414 if err != nil {
415 return
416 }
417 content, err := r.readString(m, "ModifyCacheContent content", contentPtr, contentSize)
418 if err != nil {
419 return
420 }
421 f(filename, content)
422 }
423 } else {
424 return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) {
425 filename, err := r.readASString(m, "ModifyCacheContent filename", filenamePtr)
426 if err != nil {
427 return
428 }
429 content, err := r.readASString(m, "ModifyCacheContent content", contentPtr)
430 if err != nil {
431 return
432 }
433 f(filename, content)
434 }
435 }
436}
437
438func (r *runtime) CommitAllBuilder(forAS bool) interface{} {
439 f := func(msg string, err error) {
440 if err != nil {
441 return
442 }
443 if h, err := r.repoWriter.CommitAll(msg, r.plugin.commiter); err != nil {
444 r.logger.Error("commitAll can't commit", err)
445 return
446 } else {
447 r.logger.Debug("Commit", logger.NewLoggerPair("message", msg), logger.NewLoggerPair("hash", h.String()))
448 if r.commitHook != nil && !h.IsZero() {
449 r.commitHook(h)
450 }
451 }
452 }
453 if !forAS {
454 return func(_ context.Context, m api.Module, offset, byteCount uint32) {
455 f(r.readString(m, "commitAll", offset, byteCount))
456 }
457 } else {
458 return func(_ context.Context, m api.Module, offset uint32) {
459 f(r.readASString(m, "commitAll", offset))
460 }
461 }
462}
463
464func (r *runtime) DiffWithParentBuilder(forAS bool) interface{} {
465 f := func(m api.Module, hash string, oldfile string, newfile string) uint64 {
466 r.logger.Info("In diffWithParent before found ancestor")
467 diffStr, err := r.repoWriter.GetDiff(plumbing.NewHash(string(hash)), string(oldfile), string(newfile))
468 if err != nil {
469 r.logger.Error("GetDiff", err)
470 return 0
471 }
472 r.logger.Debug("In diffWithParent send diff", logger.NewLoggerPair("diff", diffStr))
473 ptrSize, err := r.sendData(m, diffStr)
474 if err != nil {
475 r.logger.Error("can't sendData", err, logger.NewLoggerPair("message", diffStr))
476 return 0
477 }
478 return ptrSize
479 }
480 if !forAS {
481 return func(_ context.Context, m api.Module, hashPtr, hashSize, oldFilenamePtr, oldFilenameSize, newFilenamePtr, newFilenameSize uint32) uint64 {
482 hash, err := r.readString(m, "DiffWithParent hash", hashPtr, hashSize)
483 if err != nil {
484 return 0
485 }
486 oldFilename, err := r.readString(m, "DiffWithParent oldFilename", oldFilenamePtr, oldFilenameSize)
487 if err != nil {
488 return 0
489 }
490 newFilename, err := r.readString(m, "DiffWithParent newFilename", newFilenamePtr, newFilenameSize)
491 if err != nil {
492 return 0
493 }
494 return f(m, hash, oldFilename, newFilename)
495 }
496 } else {
497 return func(_ context.Context, m api.Module, hashPtr, oldFilenamePtr, newFilenamePtr uint32) uint64 {
498 hash, err := r.readASString(m, "DiffWithParent hash", hashPtr)
499 if err != nil {
500 return 0
501 }
502 oldFilename, err := r.readASString(m, "DiffWithParent oldFilenamePtr", oldFilenamePtr)
503 if err != nil {
504 return 0
505 }
506 newFilename, err := r.readASString(m, "DiffWithParent newFilename", newFilenamePtr)
507 if err != nil {
508 return 0
509 }
510 return f(m, hash, oldFilename, newFilename)
511 }
512 }
513}
514
515func (r *runtime) LogBuilder(forAS bool) interface{} {
516 f := func(msg string, err error) {
517 if err != nil {
518 return
519 }
520 repoName := ""
521 if r.repo != nil { //in forconf scenario their is no repo
522 repoName = r.repo.Name()
523 }
524 r.logger.Debug(msg, logger.NewLoggerPair("repo", repoName), logger.NewLoggerPair("plugin", r.plugin.Name))
525 }
526 if !forAS {
527 return func(_ context.Context, m api.Module, offset, byteCount uint32) {
528 f(r.readString(m, "log", offset, byteCount))
529 }
530 } else {
531 return func(_ context.Context, m api.Module, offset uint32) {
532 f(r.readASString(m, "log", offset))
533 }
534 }
535}
536
537func (r *runtime) LogErrorBuilder(forAS bool) interface{} {
538 f := func(msg string, err string) {
539 repoName := ""
540 if r.repo != nil { //in forconf scenario their is no repo
541 repoName = r.repo.Name()
542 }
543 r.logger.Error(msg, errors.New(err), logger.NewLoggerPair("repo", repoName), logger.NewLoggerPair("plugin", r.plugin.Name))
544 }
545 if !forAS {
546 return func(_ context.Context, m api.Module, offset, byteCount, errPtr, errSize uint32) {
547 msg, err := r.readString(m, "LogError msg", offset, byteCount)
548 if err != nil {
549 return
550 }
551 errMsg, err := r.readString(m, "LogError err", errPtr, errSize)
552 if err != nil {
553 return
554 }
555 f(msg, errMsg)
556 }
557 } else {
558 return func(_ context.Context, m api.Module, offset, errPtr uint32) {
559 msg, err := r.readASString(m, "LogError msg", offset)
560 if err != nil {
561 return
562 }
563 errMsg, err := r.readASString(m, "LogError err", errPtr)
564 if err != nil {
565 return
566 }
567 f(msg, errMsg)
568 }
569 }
570}
571
572func (r *runtime) MergeBuilder(forAS bool) interface{} {
573 f := func(from string, to string) {
574 r.logger.Debug("try to merge", logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
575 cmd, err := r.repoWriter.Merge(from, to, r.plugin.commiter, r.command.pusher)
576 if err != nil {
577 r.logger.Error("can't Merge", err, logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
578 }
579 if r.mergeHook != nil {
580 r.mergeHook(cmd, r.command.pusher, to)
581 }
582 }
583 if !forAS {
584 return func(_ context.Context, m api.Module, fromPtr, fromSize, toPtr, toSize uint32) {
585 from, err := r.readString(m, "Merge from", fromPtr, fromSize)
586 if err != nil {
587 return
588 }
589 to, err := r.readString(m, "Merge to", toPtr, toSize)
590 if err != nil {
591 return
592 }
593 f(from, to)
594 }
595 } else {
596 return func(_ context.Context, m api.Module, fromPtr, toPtr uint32) {
597 from, err := r.readASString(m, "Merge from", fromPtr)
598 if err != nil {
599 return
600 }
601 to, err := r.readASString(m, "Merge to", toPtr)
602 if err != nil {
603 return
604 }
605 f(from, to)
606 }
607 }
608}
609
610func (r *runtime) GetCommitsBuilder(forAS bool) interface{} {
611 f := func(m api.Module, from string, to string) uint64 {
612 commits := []commitForDiffCommit{}
613 if err := r.repoWriter.WalkCommit(plumbing.NewHash(to), plumbing.NewHash(from), func(c *object.Commit) error {
614 r.logger.Debug("found commit", logger.NewLoggerPair("hash", c.Hash.String()), logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
615 commits = append(commits, commitToCommitForDiff(c, nil))
616 return nil
617 }); err != nil {
618 r.logger.Error("can't WalkCommit", err, logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
619 }
620 r.logger.Debug("found commits", logger.NewLoggerPair("nb", len(commits)), logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
621 j, err := Marshall(r.command.branch.Short(), commits)
622 if err != nil {
623 r.logger.Error("can't Marshall", err, logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
624 }
625 ptrSize, err := r.sendData(m, j)
626 if err != nil {
627 r.logger.Error("can't sendData", err, logger.NewLoggerPair("message", j))
628 return 0
629 }
630 return ptrSize
631 }
632 if !forAS {
633 return func(_ context.Context, m api.Module, fromPtr, fromSize, toPtr, toSize uint32) uint64 {
634 from, err := r.readString(m, "Merge from", fromPtr, fromSize)
635 if err != nil {
636 return 0
637 }
638 to, err := r.readString(m, "Merge to", toPtr, toSize)
639 if err != nil {
640 return 0
641 }
642 return f(m, from, to)
643 }
644 } else {
645 return func(_ context.Context, m api.Module, fromPtr, toPtr uint32) uint64 {
646 from, err := r.readASString(m, "Merge from", fromPtr)
647 if err != nil {
648 return 0
649 }
650 to, err := r.readASString(m, "Merge to", toPtr)
651 if err != nil {
652 return 0
653 }
654 return f(m, from, to)
655 }
656 }
657}
658
659func (r *runtime) ExecBuilder(forAS bool) interface{} {
660 f := func(m api.Module, cmd string) uint64 {
661 exec := pluginLib.Exec{}
662 err := json.Unmarshal([]byte(cmd), &exec)
663 if err != nil {
664 r.logger.Error("can't exec bad format", err)
665 return 0
666 }
667 if ok, err := checkExec(r.pluginRun.write.exec, exec); !ok {
668 r.logger.Warn("plugin can't exec", logger.NewLoggerPair("err", err), logger.NewLoggerPair("repo", r.repo.Name()), logger.NewLoggerPair("branch", r.command.branch), logger.NewLoggerPair("plugin", r.plugin.Name))
669 return 0
670 }
671 j, err := r.manager.execManager.Exec(r.repo, r.command.branch.Short(), exec)
672 if err != nil {
673 r.logger.Error("can't exec", err)
674 return 0
675 }
676 jjson, err := json.Marshal(j)
677 if err != nil {
678 r.logger.Error("can't marshal execStatus", err)
679 return 0
680 }
681 ptrSize, err := r.sendData(m, string(jjson))
682 if err != nil {
683 r.logger.Error("can't sendData", err, logger.NewLoggerPair("message", j))
684 return 0
685 }
686 return ptrSize
687 }
688 if !forAS {
689 return func(_ context.Context, m api.Module, commandPtr, commandSize uint32) uint64 {
690 cmd, err := r.readString(m, "exec cmd", commandPtr, commandSize)
691 if err != nil {
692 return 0
693 }
694 return f(m, cmd)
695 }
696 } else {
697 return func(_ context.Context, m api.Module, commandPtr uint32) uint64 {
698 cmd, err := r.readASString(m, "exec cmd", commandPtr)
699 if err != nil {
700 return 0
701 }
702 return f(m, cmd)
703 }
704 }
705}
706
707func (r *runtime) ReportBuilder(forAS bool) interface{} {
708 f := func(reportJson string) {
709 if r.command == nil || r.commit == nil {
710 r.logger.Error("can't report in conf stage", errors.New("report not allowed"))
711 return
712 }
713 report := pluginLib.ReportToGitroot{}
714 err := json.Unmarshal([]byte(reportJson), &report)
715 if err != nil {
716 r.logger.Error("can't unmarshal reportJson bad format", err)
717 return
718 }
719 re := pluginLib.Report{
720 Level: report.Level,
721 Content: report.Content,
722 FromPlugin: r.plugin.Name,
723 FromBranch: r.command.branch.Short(),
724 FromCommit: r.commit.hash.String(),
725 }
726 reJson, err := json.Marshal(re)
727 if err != nil {
728 r.logger.Error("can't marshal reJson bad format", err)
729 return
730 }
731 r.logger.Debug("report added", logger.NewLoggerPair("report", reJson))
732 r.reports = append(r.reports, re)
733 }
734 if !forAS {
735 return func(_ context.Context, m api.Module, reportPtr, reportSize uint32) {
736 reportJson, err := r.readString(m, "report json", reportPtr, reportSize)
737 if err != nil {
738 return
739 }
740 f(reportJson)
741 }
742 } else {
743 return func(_ context.Context, m api.Module, reportPtr uint32) {
744 reportJson, err := r.readASString(m, "report json", reportPtr)
745 if err != nil {
746 return
747 }
748 f(reportJson)
749 }
750 }
751}
752
753func (r *runtime) Close() error {
754 return r.wazRun.Close(r.ctx)
755}
756
757func (r *runtime) sendData(module api.Module, message string) (ptrSizeToSend uint64, err error) {
758 malloc := module.ExportedFunction("gitrootAlloc")
759 if malloc == nil {
760 malloc = module.ExportedFunction("malloc")
761 }
762
763 s := uint64(len(message))
764
765 results, err := malloc.Call(r.ctx, s)
766 if err != nil {
767 return 0, oops.Wrapf(err, "can't malloc memory")
768 }
769 ptrA := results[0]
770
771 // The pointer is a linear memory offset, which is where we write the name.
772 if !module.Memory().WriteString(uint32(ptrA), message) {
773 return 0, oops.Errorf("can't write memory")
774 }
775
776 r.memoryToGarbage = append(r.memoryToGarbage, ptrSize{ptr: ptrA, size: s})
777
778 return (ptrA << uint64(32)) | s, nil
779}
780
781func (r *runtime) readString(m api.Module, errorMsg string, ptr uint32, size uint32) (string, error) {
782 mem := m.Memory()
783 if mem == nil {
784 err := errors.New("memory not exist")
785 r.logger.Error(errorMsg, err, logger.NewLoggerPair("ptr", ptr), logger.NewLoggerPair("size", size))
786 return "", err
787 }
788 txt, ok := mem.Read(ptr, size)
789 if !ok {
790 err := errors.New("memory read fail")
791 r.logger.Error(errorMsg, err, logger.NewLoggerPair("ptr", ptr), logger.NewLoggerPair("size", size))
792 return "", err
793 }
794 return string(txt), nil
795}
796
797func (r *runtime) readASString(m api.Module, errorMsg string, offset uint32) (string, error) {
798 // Length is four bytes before pointer.
799 byteCount, ok := m.Memory().ReadUint32Le(offset - 4)
800 if !ok || byteCount%2 != 0 {
801 err := errors.New("Memory.ReadUint32Le fail")
802 r.logger.Error(errorMsg, err, logger.NewLoggerPair("byteCount", byteCount), logger.NewLoggerPair("offset", offset))
803 return "", err
804 }
805 buf, ok := m.Memory().Read(offset, byteCount)
806 if !ok {
807 err := errors.New("Memory.Read fail")
808 r.logger.Error(errorMsg, err, logger.NewLoggerPair("offset", offset), logger.NewLoggerPair("byteCount", byteCount))
809 return "", err
810 }
811 u16s := make([]uint16, len(buf)/2)
812
813 lb := len(buf)
814 for i := 0; i < lb; i += 2 {
815 u16s[i/2] = uint16(buf[i]) + (uint16(buf[i+1]) << 8)
816 }
817 return string(utf16.Decode(u16s)), nil
818}
819
820type ptrSize struct {
821 ptr uint64
822 size uint64
823}
824
825func (r *runtime) free(module api.Module, ptrSize []ptrSize) error {
826 free := module.ExportedFunction("gitrootFree")
827 if free == nil {
828 free = module.ExportedFunction("free")
829 }
830
831 if free != nil && len(free.Definition().ParamTypes()) == 1 {
832 for _, p := range ptrSize {
833 _, err := free.Call(r.ctx, p.ptr)
834 if err != nil {
835 r.logger.Error("can't free pluginConf", err)
836 }
837 }
838 } else if free != nil {
839 for _, p := range ptrSize {
840 _, err := free.Call(r.ctx, p.ptr, p.size)
841 if err != nil {
842 r.logger.Error("can't free pluginConf", err)
843 }
844 }
845 }
846
847 return nil
848}