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