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