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}