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