GitRoot

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