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 forgeConf, err := json.Marshal(r.manager.conf.ForgeConf())
300 if err != nil {
301 r.logger.Error("ForgeConf can't be serialized", err)
302 return 0
303 }
304 _, ptrSize, err := r.sendData(m, string(forgeConf)) //TODO free ptr (first arg)
305 if err != nil {
306 r.logger.Error("ForgeConf can't send data", err)
307 return 0
308 }
309 return ptrSize
310}
311
312func (r *runtime) ModifyContentBuilder(forAS bool) interface{} {
313 f := func(filename string, content string) {
314 if ok, err := checkWrite(r.pluginRun.write.git, r.repoWriter.ToFs(r.ctx), filename); !ok {
315 r.logger.Error("plugin can't write in git", err, logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename))
316 return
317 }
318 r.logger.Debug("modifyContent", logger.NewLoggerPair("file", filename))
319 if err := r.repoWriter.Write(filename, []byte(content)); err != nil {
320 r.logger.Error("modifyContent can't open file", err, logger.NewLoggerPair("filepath", filename))
321 return
322 }
323 }
324
325 if !forAS {
326 return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) {
327 filename, err := r.readString(m, "ModifyContent filename", filenamePtr, filenameSize)
328 if err != nil {
329 return
330 }
331 content, err := r.readString(m, "ModifyContent content", contentPtr, contentSize)
332 if err != nil {
333 return
334 }
335 f(filename, content)
336 }
337 } else {
338 return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) {
339 filename, err := r.readASString(m, "ModifyContent filename", filenamePtr)
340 if err != nil {
341 return
342 }
343 content, err := r.readASString(m, "ModifyContent content", contentPtr)
344 if err != nil {
345 return
346 }
347 f(filename, content)
348 }
349 }
350}
351
352func (r *runtime) ModifyWebContentBuilder(forAS bool) interface{} {
353 f := func(filename string, content string) {
354 if r.repo == nil { //in forconf scenario their is no repo
355 r.logger.Error("ModifyWebContentBuilder can't in forconf", errors.New("no repo"), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("filepath", filename))
356 return
357 }
358 if ok, err := checkWrite(r.pluginRun.write.web, r.manager.conf.DataWeb(r.repo.Name()), filename); !ok {
359 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))
360 return
361 }
362 fullPath := r.repo.PathDataWeb(filename)
363 dir, _ := filepath.Split(fullPath)
364 if err := os.MkdirAll(dir, os.ModePerm); err != nil {
365 r.logger.Error("modifyWebContent can't mkdirAll", err, logger.NewLoggerPair("filepath", filename))
366 return
367 }
368 if err := os.WriteFile(fullPath, []byte(content), 0666); err != nil {
369 r.logger.Error("modifyWebContent can't open file", err, logger.NewLoggerPair("filepath", filename))
370 return
371 }
372 r.logger.Debug("Write in web", logger.NewLoggerPair("fullPath", fullPath))
373 }
374
375 if !forAS {
376 return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) {
377 filename, err := r.readString(m, "ModifyWebContent filename", filenamePtr, filenameSize)
378 if err != nil {
379 return
380 }
381 content, err := r.readString(m, "ModifyWebContent content", contentPtr, contentSize)
382 if err != nil {
383 return
384 }
385 f(filename, content)
386 }
387 } else {
388 return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) {
389 filename, err := r.readASString(m, "ModifyWebContent filename", filenamePtr)
390 if err != nil {
391 return
392 }
393 content, err := r.readASString(m, "ModifyWebContent content", contentPtr)
394 if err != nil {
395 return
396 }
397 f(filename, content)
398 }
399 }
400}
401
402func (r *runtime) ReplaceWebContentBuilder(forAS bool) interface{} {
403 f := func(filename string, oldContent string, content string) {
404 if r.repo == nil { //in forconf scenario their is no repo
405 r.logger.Error("ReplaceWebContentBuilder can't in forconf", errors.New("no repo"), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("filepath", filename))
406 return
407 }
408 if ok, err := checkWrite(r.pluginRun.write.web, r.manager.conf.DataWeb(r.repo.Name()), filename); !ok {
409 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))
410 return
411 }
412 fullPath := r.repo.PathDataWeb(filename)
413 fileContent, err := os.ReadFile(fullPath)
414 if err != nil {
415 r.logger.Error("ReplaceWebContentBuilder can't ReadFile", err, logger.NewLoggerPair("filepath", filename))
416 return
417 }
418 if err := os.WriteFile(fullPath, bytes.Replace(fileContent, []byte(oldContent), []byte(content), 1), 0666); err != nil {
419 r.logger.Error("ReplaceWebContentBuilder can't open file", err, logger.NewLoggerPair("filepath", filename))
420 return
421 }
422 r.logger.Debug("Write in web", logger.NewLoggerPair("fullPath", fullPath))
423 }
424
425 if !forAS {
426 return func(_ context.Context, m api.Module, filenamePtr, filenameSize, oldContentPtr, oldContentSize, contentPtr, contentSize uint32) {
427 filename, err := r.readString(m, "ReplaceWebContentBuilder filename", filenamePtr, filenameSize)
428 if err != nil {
429 return
430 }
431 oldContent, err := r.readString(m, "ReplaceWebContentBuilder oldContent", oldContentPtr, oldContentSize)
432 if err != nil {
433 return
434 }
435 content, err := r.readString(m, "ReplaceWebContentBuilder content", contentPtr, contentSize)
436 if err != nil {
437 return
438 }
439 f(filename, oldContent, content)
440 }
441 } else {
442 return func(_ context.Context, m api.Module, filenamePtr, oldContentPtr, contentPtr uint32) {
443 filename, err := r.readASString(m, "ReplaceWebContentBuilder filename", filenamePtr)
444 if err != nil {
445 return
446 }
447 oldContent, err := r.readASString(m, "ReplaceWebContentBuilder oldContent", oldContentPtr)
448 if err != nil {
449 return
450 }
451 content, err := r.readASString(m, "ReplaceWebContentBuilder content", contentPtr)
452 if err != nil {
453 return
454 }
455 f(filename, oldContent, content)
456 }
457 }
458}
459
460func (r *runtime) ModifyCacheContentBuilder(forAS bool) interface{} {
461 f := func(filename string, content string) {
462 r.logger.Debug("ModifyCacheContent", logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("file", filename))
463 if r.repo == nil { //in forconf scenario their is no repo
464 r.logger.Error("ModifyCacheContent can't in forconf", errors.New("no repo"), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("filepath", filename))
465 return
466 }
467 fullPath := filepath.Join(r.manager.conf.PathCache(), r.repo.Name(), r.plugin.Name, filename)
468 dir, _ := filepath.Split(fullPath)
469 if err := os.MkdirAll(dir, os.ModePerm); err != nil {
470 r.logger.Error("ModifyCacheContent can't mkdirAll", err, logger.NewLoggerPair("filepath", filename))
471 return
472 }
473 if err := os.WriteFile(fullPath, []byte(content), 0666); err != nil {
474 r.logger.Error("ModifyCacheContent can't open file", err, logger.NewLoggerPair("filepath", filename))
475 return
476 }
477 r.logger.Debug("Write in cache", logger.NewLoggerPair("fullPath", fullPath))
478 }
479 if !forAS {
480 return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) {
481 filename, err := r.readString(m, "ModifyCacheContent filename", filenamePtr, filenameSize)
482 if err != nil {
483 return
484 }
485 content, err := r.readString(m, "ModifyCacheContent content", contentPtr, contentSize)
486 if err != nil {
487 return
488 }
489 f(filename, content)
490 }
491 } else {
492 return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) {
493 filename, err := r.readASString(m, "ModifyCacheContent filename", filenamePtr)
494 if err != nil {
495 return
496 }
497 content, err := r.readASString(m, "ModifyCacheContent content", contentPtr)
498 if err != nil {
499 return
500 }
501 f(filename, content)
502 }
503 }
504}
505
506func (r *runtime) CommitAllBuilder(forAS bool) interface{} {
507 f := func(msg string, err error) {
508 if err != nil {
509 return
510 }
511 if h, err := r.repoWriter.CommitAll(msg, r.plugin.commiter); err != nil {
512 r.logger.Error("commitAll can't commit", err)
513 return
514 } else {
515 r.logger.Debug("Commit", logger.NewLoggerPair("message", msg), logger.NewLoggerPair("hash", h.String()))
516 if r.commitHook != nil && !h.IsZero() {
517 r.commitHook(h)
518 }
519 }
520 }
521 if !forAS {
522 return func(_ context.Context, m api.Module, offset, byteCount uint32) {
523 f(r.readString(m, "commitAll", offset, byteCount))
524 }
525 } else {
526 return func(_ context.Context, m api.Module, offset uint32) {
527 f(r.readASString(m, "commitAll", offset))
528 }
529 }
530}
531
532func (r *runtime) DiffWithParentBuilder(forAS bool) interface{} {
533 f := func(m api.Module, hash string, oldfile string, newfile string) uint64 {
534 r.logger.Info("In diffWithParent before found ancestor")
535 diffStr, err := r.repoWriter.GetDiff(plumbing.NewHash(string(hash)), string(oldfile), string(newfile))
536 if err != nil {
537 r.logger.Error("GetDiff", err)
538 return 0
539 }
540 r.logger.Debug("In diffWithParent send diff", logger.NewLoggerPair("diff", diffStr))
541 _, ptrSize, err := r.sendData(m, diffStr) //TODO free ptr (first arg)
542 if err != nil {
543 r.logger.Error("can't sendData", err, logger.NewLoggerPair("message", diffStr))
544 return 0
545 }
546 return ptrSize
547 }
548 if !forAS {
549 return func(_ context.Context, m api.Module, hashPtr, hashSize, oldFilenamePtr, oldFilenameSize, newFilenamePtr, newFilenameSize uint32) uint64 {
550 hash, err := r.readString(m, "DiffWithParent hash", hashPtr, hashSize)
551 if err != nil {
552 return 0
553 }
554 oldFilename, err := r.readString(m, "DiffWithParent oldFilename", oldFilenamePtr, oldFilenameSize)
555 if err != nil {
556 return 0
557 }
558 newFilename, err := r.readString(m, "DiffWithParent newFilename", newFilenamePtr, newFilenameSize)
559 if err != nil {
560 return 0
561 }
562 return f(m, hash, oldFilename, newFilename)
563 }
564 } else {
565 return func(_ context.Context, m api.Module, hashPtr, oldFilenamePtr, newFilenamePtr uint32) uint64 {
566 hash, err := r.readASString(m, "DiffWithParent hash", hashPtr)
567 if err != nil {
568 return 0
569 }
570 oldFilename, err := r.readASString(m, "DiffWithParent oldFilenamePtr", oldFilenamePtr)
571 if err != nil {
572 return 0
573 }
574 newFilename, err := r.readASString(m, "DiffWithParent newFilename", newFilenamePtr)
575 if err != nil {
576 return 0
577 }
578 return f(m, hash, oldFilename, newFilename)
579 }
580 }
581}
582
583func (r *runtime) LogBuilder(forAS bool) interface{} {
584 f := func(msg string, err error) {
585 if err != nil {
586 return
587 }
588 repoName := ""
589 if r.repo != nil { //in forconf scenario their is no repo
590 repoName = r.repo.Name()
591 }
592 r.logger.Debug(msg, logger.NewLoggerPair("repo", repoName), logger.NewLoggerPair("plugin", r.plugin.Name))
593 }
594 if !forAS {
595 return func(_ context.Context, m api.Module, offset, byteCount uint32) {
596 f(r.readString(m, "log", offset, byteCount))
597 }
598 } else {
599 return func(_ context.Context, m api.Module, offset uint32) {
600 f(r.readASString(m, "log", offset))
601 }
602 }
603}
604
605func (r *runtime) LogErrorBuilder(forAS bool) interface{} {
606 f := func(msg string, err string) {
607 repoName := ""
608 if r.repo != nil { //in forconf scenario their is no repo
609 repoName = r.repo.Name()
610 }
611 r.logger.Error(msg, errors.New(err), logger.NewLoggerPair("repo", repoName), logger.NewLoggerPair("plugin", r.plugin.Name))
612 }
613 if !forAS {
614 return func(_ context.Context, m api.Module, offset, byteCount, errPtr, errSize uint32) {
615 msg, err := r.readString(m, "LogError msg", offset, byteCount)
616 if err != nil {
617 return
618 }
619 errMsg, err := r.readString(m, "LogError err", errPtr, errSize)
620 if err != nil {
621 return
622 }
623 f(msg, errMsg)
624 }
625 } else {
626 return func(_ context.Context, m api.Module, offset, errPtr uint32) {
627 msg, err := r.readASString(m, "LogError msg", offset)
628 if err != nil {
629 return
630 }
631 errMsg, err := r.readASString(m, "LogError err", errPtr)
632 if err != nil {
633 return
634 }
635 f(msg, errMsg)
636 }
637 }
638}
639
640func (r *runtime) MergeBuilder(forAS bool) interface{} {
641 f := func(from string, to string) {
642 r.logger.Debug("try to merge", logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
643 cmd, err := r.repoWriter.Merge(from, to, r.plugin.commiter, r.command.pusher)
644 if err != nil {
645 r.logger.Error("can't Merge", err, logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
646 }
647 if r.mergeHook != nil {
648 r.mergeHook(cmd, r.command.pusher, to)
649 }
650 }
651 if !forAS {
652 return func(_ context.Context, m api.Module, fromPtr, fromSize, toPtr, toSize uint32) {
653 from, err := r.readString(m, "Merge from", fromPtr, fromSize)
654 if err != nil {
655 return
656 }
657 to, err := r.readString(m, "Merge to", toPtr, toSize)
658 if err != nil {
659 return
660 }
661 f(from, to)
662 }
663 } else {
664 return func(_ context.Context, m api.Module, fromPtr, toPtr uint32) {
665 from, err := r.readASString(m, "Merge from", fromPtr)
666 if err != nil {
667 return
668 }
669 to, err := r.readASString(m, "Merge to", toPtr)
670 if err != nil {
671 return
672 }
673 f(from, to)
674 }
675 }
676}
677
678func (r *runtime) GetCommitsBuilder(forAS bool) interface{} {
679 f := func(m api.Module, from string, to string) uint64 {
680 commits := []commitForDiffCommit{}
681 if err := r.repoWriter.WalkCommit(plumbing.NewHash(to), plumbing.NewHash(from), func(c *object.Commit) error {
682 r.logger.Debug("found commit", logger.NewLoggerPair("hash", c.Hash.String()), logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
683 commits = append(commits, commitToCommitForDiff(c, nil))
684 return nil
685 }); err != nil {
686 r.logger.Error("can't WalkCommit", err, logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
687 }
688 r.logger.Debug("found commits", logger.NewLoggerPair("nb", len(commits)), logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
689 j, err := Marshall("//TODO", commits)
690 if err != nil {
691 r.logger.Error("can't Marshall", err, logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
692 }
693 _, ptrSize, err := r.sendData(m, j) //TODO free ptr (first arg)
694 if err != nil {
695 r.logger.Error("can't sendData", err, logger.NewLoggerPair("message", j))
696 return 0
697 }
698 return ptrSize
699 }
700 if !forAS {
701 return func(_ context.Context, m api.Module, fromPtr, fromSize, toPtr, toSize uint32) uint64 {
702 from, err := r.readString(m, "Merge from", fromPtr, fromSize)
703 if err != nil {
704 return 0
705 }
706 to, err := r.readString(m, "Merge to", toPtr, toSize)
707 if err != nil {
708 return 0
709 }
710 return f(m, from, to)
711 }
712 } else {
713 return func(_ context.Context, m api.Module, fromPtr, toPtr uint32) uint64 {
714 from, err := r.readASString(m, "Merge from", fromPtr)
715 if err != nil {
716 return 0
717 }
718 to, err := r.readASString(m, "Merge to", toPtr)
719 if err != nil {
720 return 0
721 }
722 return f(m, from, to)
723 }
724 }
725}
726
727func (r *runtime) ExecBuilder(forAS bool) interface{} {
728 f := func(m api.Module, cmd string) uint64 {
729 execs := []pluginLib.Exec{}
730 err := json.Unmarshal([]byte(cmd), &execs)
731 if err != nil {
732 r.logger.Error("can't exec bad format", err)
733 return 0
734 }
735 if ok, err := checkExec(r.pluginRun.write.exec, execs); !ok {
736 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))
737 return 0
738 }
739 j, err := r.manager.execManager.Exec(r.repo, execs)
740 if err != nil {
741 r.logger.Error("can't exec", err)
742 return 0
743 }
744 _, ptrSize, err := r.sendData(m, j) //TODO free ptr (first arg)
745 if err != nil {
746 r.logger.Error("can't sendData", err, logger.NewLoggerPair("message", j))
747 return 0
748 }
749 return ptrSize
750 }
751 if !forAS {
752 return func(_ context.Context, m api.Module, commandPtr, commandSize uint32) uint64 {
753 cmd, err := r.readString(m, "exec cmd", commandPtr, commandSize)
754 if err != nil {
755 return 0
756 }
757 return f(m, cmd)
758 }
759 } else {
760 return func(_ context.Context, m api.Module, commandPtr uint32) uint64 {
761 cmd, err := r.readASString(m, "exec cmd", commandPtr)
762 if err != nil {
763 return 0
764 }
765 return f(m, cmd)
766 }
767 }
768}
769
770func (r *runtime) Close() error {
771 return r.wazRun.Close(r.ctx)
772}
773
774func (r *runtime) sendData(module api.Module, message string) (ptr uint32, ptrSize uint64, err error) {
775 malloc := module.ExportedFunction("gitrootAlloc")
776 if malloc == nil {
777 malloc = module.ExportedFunction("malloc")
778 }
779
780 s := uint64(len(message))
781
782 results, err := malloc.Call(r.ctx, s)
783 if err != nil {
784 return 0, 0, oops.Wrapf(err, "can't malloc memory")
785 }
786 ptrA := results[0]
787
788 // The pointer is a linear memory offset, which is where we write the name.
789 if !module.Memory().WriteString(uint32(ptrA), message) {
790 return 0, 0, oops.Errorf("can't write memory")
791 }
792
793 return uint32(ptrA), (ptrA << uint64(32)) | s, nil
794}
795
796func (r *runtime) readString(m api.Module, errorMsg string, ptr uint32, size uint32) (string, error) {
797 mem := m.Memory()
798 if mem == nil {
799 err := errors.New("memory not exist")
800 r.logger.Error(errorMsg, err, logger.NewLoggerPair("ptr", ptr), logger.NewLoggerPair("size", size))
801 return "", err
802 }
803 txt, ok := mem.Read(ptr, size)
804 if !ok {
805 err := errors.New("memory read fail")
806 r.logger.Error(errorMsg, err, logger.NewLoggerPair("ptr", ptr), logger.NewLoggerPair("size", size))
807 return "", err
808 }
809 return string(txt), nil
810}
811
812func (r *runtime) readASString(m api.Module, errorMsg string, offset uint32) (string, error) {
813 // Length is four bytes before pointer.
814 byteCount, ok := m.Memory().ReadUint32Le(offset - 4)
815 if !ok || byteCount%2 != 0 {
816 err := errors.New("Memory.ReadUint32Le fail")
817 r.logger.Error(errorMsg, err, logger.NewLoggerPair("byteCount", byteCount), logger.NewLoggerPair("offset", offset))
818 return "", err
819 }
820 buf, ok := m.Memory().Read(offset, byteCount)
821 if !ok {
822 err := errors.New("Memory.Read fail")
823 r.logger.Error(errorMsg, err, logger.NewLoggerPair("offset", offset), logger.NewLoggerPair("byteCount", byteCount))
824 return "", err
825 }
826 u16s := make([]uint16, len(buf)/2)
827
828 lb := len(buf)
829 for i := 0; i < lb; i += 2 {
830 u16s[i/2] = uint16(buf[i]) + (uint16(buf[i+1]) << 8)
831 }
832 return string(utf16.Decode(u16s)), nil
833}