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	"slices"
  18	"unicode/utf16"
  19
  20	"github.com/go-git/go-git/v5/plumbing"
  21	"github.com/go-git/go-git/v5/plumbing/object"
  22	"github.com/go-git/go-git/v5/plumbing/protocol/packp"
  23	"github.com/samber/oops"
  24	"github.com/tetratelabs/wazero"
  25	"github.com/tetratelabs/wazero/api"
  26	"github.com/tetratelabs/wazero/imports/assemblyscript"
  27	"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
  28	"github.com/tetratelabs/wazero/sys"
  29	pluginLib "gitroot.dev/libs/golang/plugin/model"
  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	close    func()
  49}
  50
  51type memoryGarbage struct {
  52	module  api.Module
  53	ptrSize ptrSize
  54}
  55
  56type runtime struct {
  57	ctx              context.Context
  58	manager          *Manager
  59	logger           *logger.Logger
  60	repo             *repository.GitRootRepository
  61	repoWriter       *repository.GitRootRepositoryWrite
  62	plugin           Plugin
  63	pluginRun        PluginRun
  64	command          *CommandForDiff
  65	commit           *commitForDiffCommit
  66	wazRun           wazero.Runtime
  67	commitHook       func(h plumbing.Hash)
  68	mergeHook        func(cmd *packp.Command, pusher user.SimpleUser, toDeleteBranchName string)
  69	reports          []pluginLib.Report
  70	memoryToGarbage  []memoryGarbage
  71	modulesToGarbage []api.Module
  72}
  73
  74func newRuntime(ctx context.Context, manager *Manager, logger *logger.Logger, runtimeConfig wazero.RuntimeConfig) (*runtime, error) {
  75	r := &runtime{
  76		ctx:              ctx,
  77		manager:          manager,
  78		logger:           logger,
  79		wazRun:           wazero.NewRuntimeWithConfig(ctx, runtimeConfig),
  80		commitHook:       nil,
  81		mergeHook:        nil,
  82		memoryToGarbage:  []memoryGarbage{},
  83		modulesToGarbage: []api.Module{},
  84	}
  85
  86	_, err := assemblyscript.Instantiate(ctx, r.wazRun)
  87	if err != nil {
  88		return nil, err
  89	}
  90
  91	_, err = r.wazRun.
  92		NewHostModuleBuilder("gitroot").
  93		NewFunctionBuilder().WithFunc(r.ForgeConf).Export("forgeConf").
  94		NewFunctionBuilder().WithFunc(r.ModifyContentBuilder(false)).Export("modifyContent").
  95		NewFunctionBuilder().WithFunc(r.ModifyContentBuilder(true)).Export("modifyContentAS").
  96		NewFunctionBuilder().WithFunc(r.ModifyWebContentBuilder(false)).Export("modifyWebContent").
  97		NewFunctionBuilder().WithFunc(r.ModifyWebContentBuilder(true)).Export("modifyWebContentAS").
  98		NewFunctionBuilder().WithFunc(r.ReplaceWebContentBuilder(false)).Export("replaceWebContent").
  99		NewFunctionBuilder().WithFunc(r.ReplaceWebContentBuilder(true)).Export("replaceWebContentAS").
 100		NewFunctionBuilder().WithFunc(r.ModifyCacheContentBuilder(false)).Export("modifyCacheContent").
 101		NewFunctionBuilder().WithFunc(r.ModifyCacheContentBuilder(true)).Export("modifyCacheContentAS").
 102		NewFunctionBuilder().WithFunc(r.ReplaceContent(false)).Export("replaceContent").
 103		NewFunctionBuilder().WithFunc(r.ReplaceContent(true)).Export("replaceContentAS").
 104		NewFunctionBuilder().WithFunc(r.WriteContent(false)).Export("writeContent").
 105		NewFunctionBuilder().WithFunc(r.WriteContent(true)).Export("writeContentAS").
 106		NewFunctionBuilder().WithFunc(r.CopyFileBuilder(false)).Export("copyFile").
 107		NewFunctionBuilder().WithFunc(r.CopyFileBuilder(true)).Export("copyFileAS").
 108		NewFunctionBuilder().WithFunc(r.DeleteFileBuilder(false)).Export("deleteFile").
 109		NewFunctionBuilder().WithFunc(r.DeleteFileBuilder(true)).Export("deleteFileAS").
 110		NewFunctionBuilder().WithFunc(r.MoveFileBuilder(false)).Export("moveFile").
 111		NewFunctionBuilder().WithFunc(r.MoveFileBuilder(true)).Export("moveFileAS").
 112		NewFunctionBuilder().WithFunc(r.CommitAllBuilder(false)).Export("commitAll").
 113		NewFunctionBuilder().WithFunc(r.CommitAllBuilder(true)).Export("commitAllAS").
 114		NewFunctionBuilder().WithFunc(r.DiffWithParentBuilder(false)).Export("diffWithParent").
 115		NewFunctionBuilder().WithFunc(r.DiffWithParentBuilder(true)).Export("diffWithParentAS").
 116		NewFunctionBuilder().WithFunc(r.LogBuilder(false)).Export("log").
 117		NewFunctionBuilder().WithFunc(r.LogBuilder(true)).Export("logAS").
 118		NewFunctionBuilder().WithFunc(r.LogErrorBuilder(false)).Export("logError").
 119		NewFunctionBuilder().WithFunc(r.LogErrorBuilder(true)).Export("logErrorAS").
 120		NewFunctionBuilder().WithFunc(r.MergeBuilder(false)).Export("merge").
 121		NewFunctionBuilder().WithFunc(r.MergeBuilder(true)).Export("mergeAS").
 122		NewFunctionBuilder().WithFunc(r.GetCommitsBuilder(false)).Export("commits").
 123		NewFunctionBuilder().WithFunc(r.GetCommitsBuilder(true)).Export("commitsAS").
 124		NewFunctionBuilder().WithFunc(r.ExecBuilder(false)).Export("exec").
 125		NewFunctionBuilder().WithFunc(r.ExecBuilder(true)).Export("execAS").
 126		NewFunctionBuilder().WithFunc(r.ReportBuilder(false)).Export("report").
 127		NewFunctionBuilder().WithFunc(r.ReportBuilder(true)).Export("reportAS").
 128		NewFunctionBuilder().WithFunc(r.CanCallBuilder(false)).Export("canCall").
 129		NewFunctionBuilder().WithFunc(r.CanCallBuilder(true)).Export("canCallAS").
 130		NewFunctionBuilder().WithFunc(r.CallBuilder(false)).Export("call").
 131		NewFunctionBuilder().WithFunc(r.CallBuilder(true)).Export("callAS").
 132		Instantiate(ctx)
 133	if err != nil {
 134		return nil, err
 135	}
 136	_, err = wasi_snapshot_preview1.Instantiate(ctx, r.wazRun)
 137	if err != nil {
 138		return nil, err
 139	}
 140	return r, nil
 141}
 142
 143type hasBeenMerged struct {
 144	cmd                *packp.Command
 145	pusher             user.SimpleUser
 146	toDeleteBranchName string
 147}
 148
 149func (r *runtime) listen(c chan runtimeInputs) {
 150	postMergeActions := []hasBeenMerged{}
 151	for i := range c {
 152		r.logger.Debug("listen call", logger.NewLoggerPair("repo", i.repoName), logger.NewLoggerPair("isDiff", i.kind == runtimeInputsKindDiff), logger.NewLoggerPair("isWorktree", i.kind == runtimeInputsKindWorktree))
 153		repo, err := r.manager.repoManager.Open(logger.AddCaller(r.ctx, "runtime.listen"), i.repoName)
 154		if err != nil {
 155			r.logger.Error("open error in listen", err)
 156			repo.Close()
 157			i.close()
 158			continue
 159		}
 160		repoWriter, err := repo.WillWrite(plumbing.HEAD) //TODO should mount good branch directly inside i.commands
 161		if err != nil {
 162			r.logger.Error("will write error in listen", err, logger.NewLoggerPair("repo", repo.Name()))
 163			repo.Close()
 164			i.close()
 165			continue
 166		}
 167
 168		switch i.kind {
 169		case runtimeInputsKindDiff:
 170			timerStop := r.logger.Time(fmt.Sprintf("Timer %s all plugins", repo.Name()))
 171			err := r.start(i.ctx, repo, repoWriter, i.plugins, i.commands, func(cmd *packp.Command, pusher user.SimpleUser, toDeleteBranchName string) {
 172				if cmd != nil {
 173					postMergeActions = append(postMergeActions, hasBeenMerged{
 174						cmd:                cmd,
 175						pusher:             pusher,
 176						toDeleteBranchName: toDeleteBranchName,
 177					})
 178				}
 179			})
 180			if err != nil {
 181				r.logger.Error("start error", err)
 182			}
 183			timerStop()
 184		case runtimeInputsKindWorktree:
 185			r.logger.Info("start worktree", logger.NewLoggerPair("repo", repo.Name()))
 186			timerStop := r.logger.Time(fmt.Sprintf("Timer %s all plugins worktree", repo.Name()))
 187			if err := r.worktree(i.ctx, repo, repoWriter, i.plugins, i.commands); err != nil {
 188				r.logger.Error("start error", err)
 189			}
 190			timerStop()
 191		}
 192		r.sendReports(i.ctx, repo, repoWriter, i.plugins)
 193		r.garbage()
 194		repo.Close()
 195		for _, postMerge := range postMergeActions {
 196			r.manager.backgroundManager.DeleteBranch(i.repoName, postMerge.toDeleteBranchName)
 197			r.manager.backgroundManager.PostPush(postMerge.pusher, i.repoName, []*packp.Command{postMerge.cmd})
 198		}
 199		postMergeActions = nil
 200		r.logger.Debug("finish listen call", logger.NewLoggerPair("repo", i.repoName), logger.NewLoggerPair("isDiff", i.kind == runtimeInputsKindDiff), logger.NewLoggerPair("isWorktree", i.kind == runtimeInputsKindWorktree))
 201		i.close()
 202	}
 203}
 204
 205func (r *runtime) loadModule(ctx context.Context, plugin Plugin, withFs fs.FS) (api.Module, error) {
 206	m := r.wazRun.Module(plugin.uuid())
 207	if m != nil && m.IsClosed() {
 208		r.logger.Debug("module exist but closed", logger.NewLoggerPair("name", plugin.Name))
 209		m = nil
 210	}
 211	if m == nil {
 212		r.logger.Debug("instantiate plugin conf", logger.NewLoggerPair("name", plugin.Name), logger.NewLoggerPair("with external fs", withFs != nil))
 213		config := wazero.NewModuleConfig().
 214			WithStdout(os.Stdout).WithStderr(os.Stderr).
 215			WithSysWalltime().
 216			WithSysNanotime().
 217			WithRandSource(rand.Reader).
 218			WithName(plugin.uuid()).
 219			WithStartFunctions("_initialize", "install")
 220
 221		if withFs != nil {
 222			config = config.WithFS(withFs)
 223		} else {
 224			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))
 225		}
 226
 227		mod, err := r.wazRun.InstantiateModule(ctx, plugin.compiledModule, config)
 228		if err != nil {
 229			if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
 230				fmt.Fprintf(os.Stderr, "exit_code: %d\n", exitErr.ExitCode())
 231				return nil, err
 232			} else if !ok {
 233				return nil, err
 234			}
 235		}
 236		m = mod
 237	} else {
 238		r.logger.Debug("already exist conf", logger.NewLoggerPair("name", plugin.Name))
 239	}
 240	r.modulesToGarbage = append(r.modulesToGarbage, m)
 241	return m, nil
 242}
 243
 244func (r *runtime) garbageMemory() {
 245	byModule := make(map[api.Module][]ptrSize)
 246	for _, d := range r.memoryToGarbage {
 247		if all, ok := byModule[d.module]; ok {
 248			byModule[d.module] = append(all, d.ptrSize)
 249		} else {
 250			byModule[d.module] = []ptrSize{d.ptrSize}
 251		}
 252	}
 253	for m, ptr := range byModule {
 254		r.free(m, ptr)
 255	}
 256	r.memoryToGarbage = []memoryGarbage{}
 257}
 258
 259func (r *runtime) garbage() {
 260	r.garbageMemory()
 261	r.reports = []pluginLib.Report{}
 262	for _, m := range r.modulesToGarbage {
 263		if err := m.Close(r.ctx); err != nil {
 264			r.logger.Error("close module fail", err, logger.NewLoggerPair("name", m.Name()))
 265		}
 266	}
 267	r.modulesToGarbage = []api.Module{}
 268}
 269
 270func (r *runtime) ForgeConf(_ context.Context, m api.Module) uint64 {
 271	forgeConf, err := json.Marshal(r.manager.conf.ForgeConf())
 272	if err != nil {
 273		r.logger.Error("ForgeConf can't be serialized", err)
 274		return 0
 275	}
 276	ptrSize, err := r.sendData(m, string(forgeConf))
 277	if err != nil {
 278		r.logger.Error("ForgeConf can't send data", err)
 279		return 0
 280	}
 281	return ptrSize
 282}
 283
 284func (r *runtime) modifyContent(filename string, content string) error {
 285	if ok, err := checkWrite(r.pluginRun.write.git, r.repoWriter.ToFs(r.ctx), filename); !ok {
 286		r.logger.Error("plugin can't write in git", err, logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename))
 287		return err
 288	}
 289	r.logger.Debug("modifyContent", logger.NewLoggerPair("file", filename))
 290	if err := r.repoWriter.Write(filename, []byte(content)); err != nil {
 291		r.logger.Error("modifyContent can't open file", err, logger.NewLoggerPair("filepath", filename))
 292		return err
 293	}
 294	return nil
 295}
 296
 297// TODO delete after v0.4 is released
 298// Deprecated: Use WriteContent(fs, filepath, content string) instead
 299func (r *runtime) ModifyContentBuilder(forAS bool) interface{} {
 300	if !forAS {
 301		return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) {
 302			filename, err := r.readString(m, "ModifyContent filename", filenamePtr, filenameSize)
 303			if err != nil {
 304				return
 305			}
 306			content, err := r.readString(m, "ModifyContent content", contentPtr, contentSize)
 307			if err != nil {
 308				return
 309			}
 310			r.modifyContent(filename, content)
 311		}
 312	} else {
 313		return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) {
 314			filename, err := r.readASString(m, "ModifyContent filename", filenamePtr)
 315			if err != nil {
 316				return
 317			}
 318			content, err := r.readASString(m, "ModifyContent content", contentPtr)
 319			if err != nil {
 320				return
 321			}
 322			r.modifyContent(filename, content)
 323		}
 324	}
 325}
 326
 327func (r *runtime) modifyWebContent(filename string, content string) error {
 328	if r.repo == nil { //in forconf scenario their is no repo
 329		r.logger.Error("ModifyWebContentBuilder can't in forconf", errors.New("no repo"), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("filepath", filename))
 330		return errors.New("no repo")
 331	}
 332	if ok, err := checkWrite(r.pluginRun.write.web, r.manager.conf.DataWeb(r.repo.Name()), filename); !ok {
 333		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))
 334		return errors.New("can't write in web")
 335	}
 336	fullPath := r.repo.PathDataWeb(filename)
 337	dir, _ := filepath.Split(fullPath)
 338	if err := os.MkdirAll(dir, os.ModePerm); err != nil {
 339		r.logger.Error("modifyWebContent can't mkdirAll", err, logger.NewLoggerPair("filepath", filename))
 340		return err
 341	}
 342	if err := os.WriteFile(fullPath, []byte(content), 0666); err != nil {
 343		r.logger.Error("modifyWebContent can't open file", err, logger.NewLoggerPair("filepath", filename))
 344		return err
 345	}
 346	r.logger.Debug("Write in web", logger.NewLoggerPair("fullPath", fullPath))
 347	return nil
 348}
 349
 350// TODO delete after v0.4 is released
 351// Deprecated: Use WriteContent(fs, filepath, content string) instead
 352func (r *runtime) ModifyWebContentBuilder(forAS bool) interface{} {
 353	if !forAS {
 354		return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) {
 355			filename, err := r.readString(m, "ModifyWebContent filename", filenamePtr, filenameSize)
 356			if err != nil {
 357				return
 358			}
 359			content, err := r.readString(m, "ModifyWebContent content", contentPtr, contentSize)
 360			if err != nil {
 361				return
 362			}
 363			r.modifyWebContent(filename, content)
 364		}
 365	} else {
 366		return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) {
 367			filename, err := r.readASString(m, "ModifyWebContent filename", filenamePtr)
 368			if err != nil {
 369				return
 370			}
 371			content, err := r.readASString(m, "ModifyWebContent content", contentPtr)
 372			if err != nil {
 373				return
 374			}
 375			r.modifyWebContent(filename, content)
 376		}
 377	}
 378}
 379
 380func (r *runtime) replaceGitContent(filename string, oldContent string, content string) error {
 381	if r.repo == nil { //in forconf scenario their is no repo
 382		r.logger.Error("ReplaceWebContentBuilder can't in forconf", errors.New("no repo"), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("filepath", filename))
 383		return errors.New("no repo")
 384	}
 385	if ok, err := checkWrite(r.pluginRun.write.git, r.repoWriter.ToFs(r.ctx), filename); !ok {
 386		r.logger.Warn("plugin can't write in git for replace", logger.NewLoggerPair("err", err), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename))
 387		return err
 388	}
 389	fileContent, err := r.repo.Content(filename)
 390	if err != nil {
 391		r.logger.Error("replaceGitContent can't ReadFile", err, logger.NewLoggerPair("filepath", filename))
 392		return err
 393	}
 394	if err := r.repoWriter.Write(filename, bytes.Replace(fileContent, []byte(oldContent), []byte(content), 1)); err != nil {
 395		r.logger.Error("replaceGitContent can't open file", err, logger.NewLoggerPair("filepath", filename))
 396		return err
 397	}
 398	r.logger.Debug("Write in git", logger.NewLoggerPair("fullPath", filename))
 399	return nil
 400}
 401
 402func (r *runtime) replaceWebContent(filename string, oldContent string, content string) error {
 403	if r.repo == nil { //in forconf scenario their is no repo
 404		r.logger.Error("ReplaceWebContentBuilder can't in forconf", errors.New("no repo"), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("filepath", filename))
 405		return errors.New("no repo")
 406	}
 407	if ok, err := checkWrite(r.pluginRun.write.web, r.manager.conf.DataWeb(r.repo.Name()), filename); !ok {
 408		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))
 409		return err
 410	}
 411	fullPath := r.repo.PathDataWeb(filename)
 412	fileContent, err := os.ReadFile(fullPath)
 413	if err != nil {
 414		r.logger.Error("ReplaceWebContentBuilder can't ReadFile", err, logger.NewLoggerPair("filepath", filename))
 415		return err
 416	}
 417	if err := os.WriteFile(fullPath, bytes.Replace(fileContent, []byte(oldContent), []byte(content), 1), 0666); err != nil {
 418		r.logger.Error("ReplaceWebContentBuilder can't open file", err, logger.NewLoggerPair("filepath", filename))
 419		return err
 420	}
 421	r.logger.Debug("Write in web", logger.NewLoggerPair("fullPath", fullPath))
 422	return nil
 423}
 424
 425func (r *runtime) replaceCacheContent(filename string, oldContent string, content string) error {
 426	if r.repo == nil { //in forconf scenario their is no repo
 427		r.logger.Error("replaceCacheContent can't in forconf", errors.New("no repo"), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("filepath", filename))
 428		return errors.New("no repo")
 429	}
 430	fullPath := filepath.Join(r.manager.conf.PathCacheProject(r.repo.Name(), r.plugin.Name), filename)
 431	fileContent, err := os.ReadFile(fullPath)
 432	if err != nil {
 433		r.logger.Error("replaceCacheContent can't ReadFile", err, logger.NewLoggerPair("filepath", filename))
 434		return err
 435	}
 436	if err := os.WriteFile(fullPath, bytes.Replace(fileContent, []byte(oldContent), []byte(content), 1), 0666); err != nil {
 437		r.logger.Error("replaceCacheContent can't open file", err, logger.NewLoggerPair("filepath", filename))
 438		return err
 439	}
 440	r.logger.Debug("Write in cache", logger.NewLoggerPair("fullPath", fullPath))
 441	return nil
 442}
 443
 444// TODO delete after v0.4 is released
 445// Deprecated: Use ReplaceContent(fs, filepath, content string) instead
 446func (r *runtime) ReplaceWebContentBuilder(forAS bool) interface{} {
 447	if !forAS {
 448		return func(_ context.Context, m api.Module, filenamePtr, filenameSize, oldContentPtr, oldContentSize, contentPtr, contentSize uint32) {
 449			filename, err := r.readString(m, "ReplaceWebContentBuilder filename", filenamePtr, filenameSize)
 450			if err != nil {
 451				return
 452			}
 453			oldContent, err := r.readString(m, "ReplaceWebContentBuilder oldContent", oldContentPtr, oldContentSize)
 454			if err != nil {
 455				return
 456			}
 457			content, err := r.readString(m, "ReplaceWebContentBuilder content", contentPtr, contentSize)
 458			if err != nil {
 459				return
 460			}
 461			r.replaceWebContent(filename, oldContent, content)
 462		}
 463	} else {
 464		return func(_ context.Context, m api.Module, filenamePtr, oldContentPtr, contentPtr uint32) {
 465			filename, err := r.readASString(m, "ReplaceWebContentBuilder filename", filenamePtr)
 466			if err != nil {
 467				return
 468			}
 469			oldContent, err := r.readASString(m, "ReplaceWebContentBuilder oldContent", oldContentPtr)
 470			if err != nil {
 471				return
 472			}
 473			content, err := r.readASString(m, "ReplaceWebContentBuilder content", contentPtr)
 474			if err != nil {
 475				return
 476			}
 477			r.replaceWebContent(filename, oldContent, content)
 478		}
 479	}
 480}
 481
 482func (r *runtime) modifyCacheContent(filename string, content string) error {
 483	r.logger.Debug("ModifyCacheContent", logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("file", filename))
 484	if r.repo == nil { //in forconf scenario their is no repo
 485		r.logger.Error("ModifyCacheContent can't in forconf", errors.New("no repo"), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("filepath", filename))
 486		return errors.New("no repo")
 487	}
 488	fullPath := filepath.Join(r.manager.conf.PathCacheProject(r.repo.Name(), r.plugin.Name), filename)
 489	dir, _ := filepath.Split(fullPath)
 490	if err := os.MkdirAll(dir, os.ModePerm); err != nil {
 491		r.logger.Error("ModifyCacheContent can't mkdirAll", err, logger.NewLoggerPair("filepath", filename))
 492		return err
 493	}
 494	if err := os.WriteFile(fullPath, []byte(content), 0666); err != nil {
 495		r.logger.Error("ModifyCacheContent can't open file", err, logger.NewLoggerPair("filepath", filename))
 496		return err
 497	}
 498	r.logger.Debug("Write in cache", logger.NewLoggerPair("fullPath", fullPath))
 499	return nil
 500}
 501
 502// TODO delete after v0.4 is released
 503// Deprecated: Use WriteContent(fs, filepath, content string) instead
 504func (r *runtime) ModifyCacheContentBuilder(forAS bool) interface{} {
 505	if !forAS {
 506		return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) {
 507			filename, err := r.readString(m, "ModifyCacheContent filename", filenamePtr, filenameSize)
 508			if err != nil {
 509				return
 510			}
 511			content, err := r.readString(m, "ModifyCacheContent content", contentPtr, contentSize)
 512			if err != nil {
 513				return
 514			}
 515			r.modifyCacheContent(filename, content)
 516		}
 517	} else {
 518		return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) {
 519			filename, err := r.readASString(m, "ModifyCacheContent filename", filenamePtr)
 520			if err != nil {
 521				return
 522			}
 523			content, err := r.readASString(m, "ModifyCacheContent content", contentPtr)
 524			if err != nil {
 525				return
 526			}
 527			r.modifyCacheContent(filename, content)
 528		}
 529	}
 530}
 531
 532func (r *runtime) replaceContent(fromFs string, path string, oldContent string, content string) error {
 533	switch fromFs {
 534	case "worktree":
 535		return r.replaceGitContent(path, oldContent, content)
 536	case "webcontent":
 537		return r.replaceWebContent(path, oldContent, content)
 538	case "cache":
 539		return r.replaceCacheContent(path, oldContent, content)
 540	}
 541	return nil
 542}
 543
 544func (r *runtime) ReplaceContent(forAS bool) interface{} {
 545	if !forAS {
 546		return func(_ context.Context, m api.Module, fromFsPtr, fromFsSize, fromPathPtr, fromGlobPathSize, oldContentPtr, oldContentSize, contentPtr, contentSize uint32) uint64 {
 547			fromFs, err := r.readString(m, "MoveFile fromFs", fromFsPtr, fromFsSize)
 548			if err != nil {
 549				return r.sendEmptyOrError(m, err)
 550			}
 551			fromPath, err := r.readString(m, "MoveFile fromGlobPath", fromPathPtr, fromGlobPathSize)
 552			if err != nil {
 553				return r.sendEmptyOrError(m, err)
 554			}
 555			oldContent, err := r.readString(m, "MoveFile oldContent", oldContentPtr, oldContentSize)
 556			if err != nil {
 557				return r.sendEmptyOrError(m, err)
 558			}
 559			content, err := r.readString(m, "MoveFile content", contentPtr, contentSize)
 560			if err != nil {
 561				return r.sendEmptyOrError(m, err)
 562			}
 563			return r.sendEmptyOrError(m, r.replaceContent(fromFs, fromPath, oldContent, content))
 564		}
 565	} else {
 566		return func(_ context.Context, m api.Module, fromFsPtr, fromPathPtr, oldContentPtr, contentPtr uint32) uint64 {
 567			fromFs, err := r.readASString(m, "MoveFileAS fromFs", fromFsPtr)
 568			if err != nil {
 569				return r.sendEmptyOrError(m, err)
 570			}
 571			fromPath, err := r.readASString(m, "MoveFileAS fromGlobPath", fromPathPtr)
 572			if err != nil {
 573				return r.sendEmptyOrError(m, err)
 574			}
 575			oldContent, err := r.readASString(m, "MoveFileAS oldContent", oldContentPtr)
 576			if err != nil {
 577				return r.sendEmptyOrError(m, err)
 578			}
 579			content, err := r.readASString(m, "MoveFileAS content", contentPtr)
 580			if err != nil {
 581				return r.sendEmptyOrError(m, err)
 582			}
 583			return r.sendEmptyOrError(m, r.replaceContent(fromFs, fromPath, oldContent, content))
 584		}
 585	}
 586}
 587
 588func (r *runtime) writeContent(toFs string, path string, content string) error {
 589	switch toFs {
 590	case "worktree":
 591		return r.modifyContent(path, content)
 592	case "webcontent":
 593		return r.modifyWebContent(path, content)
 594	case "cache":
 595		return r.modifyCacheContent(path, content)
 596	}
 597	return nil
 598}
 599
 600func (r *runtime) WriteContent(forAS bool) interface{} {
 601	if !forAS {
 602		return func(_ context.Context, m api.Module, fromFsPtr, fromFsSize, fromPathPtr, fromGlobPathSize, contentPtr, contentSize uint32) uint64 {
 603			fromFs, err := r.readString(m, "MoveFile fromFs", fromFsPtr, fromFsSize)
 604			if err != nil {
 605				return r.sendEmptyOrError(m, err)
 606			}
 607			fromPath, err := r.readString(m, "MoveFile fromGlobPath", fromPathPtr, fromGlobPathSize)
 608			if err != nil {
 609				return r.sendEmptyOrError(m, err)
 610			}
 611			content, err := r.readString(m, "MoveFile content", contentPtr, contentSize)
 612			if err != nil {
 613				return r.sendEmptyOrError(m, err)
 614			}
 615			return r.sendEmptyOrError(m, r.writeContent(fromFs, fromPath, content))
 616		}
 617	} else {
 618		return func(_ context.Context, m api.Module, fromFsPtr, fromPathPtr, contentPtr uint32) uint64 {
 619			fromFs, err := r.readASString(m, "MoveFileAS fromFs", fromFsPtr)
 620			if err != nil {
 621				return r.sendEmptyOrError(m, err)
 622			}
 623			fromPath, err := r.readASString(m, "MoveFileAS fromGlobPath", fromPathPtr)
 624			if err != nil {
 625				return r.sendEmptyOrError(m, err)
 626			}
 627			content, err := r.readASString(m, "MoveFileAS content", contentPtr)
 628			if err != nil {
 629				return r.sendEmptyOrError(m, err)
 630			}
 631			return r.sendEmptyOrError(m, r.writeContent(fromFs, fromPath, content))
 632		}
 633	}
 634}
 635
 636func (r *runtime) copyFile(fromFs, fromPath, toFs, toPath string) error {
 637	contentFrom := []byte("")
 638	var err error
 639	switch fromFs {
 640	case "worktree":
 641		contentFrom, err = r.repo.Content(fromPath)
 642	case "webcontent":
 643		fullPath := r.repo.PathDataWeb(fromPath)
 644		contentFrom, err = os.ReadFile(fullPath)
 645	case "cache":
 646		fullPath := filepath.Join(r.manager.conf.PathCacheProject(r.repo.Name(), r.plugin.Name), fromPath)
 647		contentFrom, err = os.ReadFile(fullPath)
 648	}
 649	if err != nil {
 650		return err
 651	}
 652	return r.writeContent(toFs, toPath, string(contentFrom))
 653}
 654
 655func (r *runtime) CopyFileBuilder(forAS bool) interface{} {
 656	if !forAS {
 657		return func(_ context.Context, m api.Module, fromFsPtr, fromFsSize, fromPathPtr, fromGlobPathSize, toFsPtr, toFsSize, toPathPtr, toPathSize uint32) uint64 {
 658			fromFs, err := r.readString(m, "MoveFile fromFs", fromFsPtr, fromFsSize)
 659			if err != nil {
 660				return r.sendEmptyOrError(m, err)
 661			}
 662			fromPath, err := r.readString(m, "MoveFile fromGlobPath", fromPathPtr, fromGlobPathSize)
 663			if err != nil {
 664				return r.sendEmptyOrError(m, err)
 665			}
 666			toFs, err := r.readString(m, "MoveFile toFs", toFsPtr, toFsSize)
 667			if err != nil {
 668				return r.sendEmptyOrError(m, err)
 669			}
 670			toPath, err := r.readString(m, "MoveFile toPath", toPathPtr, toPathSize)
 671			if err != nil {
 672				return r.sendEmptyOrError(m, err)
 673			}
 674			return r.sendEmptyOrError(m, r.copyFile(fromFs, fromPath, toFs, toPath))
 675		}
 676	} else {
 677		return func(_ context.Context, m api.Module, fromFsPtr, fromPathPtr, toFsPtr, toPathPtr uint32) uint64 {
 678			fromFs, err := r.readASString(m, "MoveFileAS fromFs", fromFsPtr)
 679			if err != nil {
 680				return r.sendEmptyOrError(m, err)
 681			}
 682			fromPath, err := r.readASString(m, "MoveFileAS fromGlobPath", fromPathPtr)
 683			if err != nil {
 684				return r.sendEmptyOrError(m, err)
 685			}
 686			toFs, err := r.readASString(m, "MoveFileAS toFs", toFsPtr)
 687			if err != nil {
 688				return r.sendEmptyOrError(m, err)
 689			}
 690			toPath, err := r.readASString(m, "MoveFileAS toPath", toPathPtr)
 691			if err != nil {
 692				return r.sendEmptyOrError(m, err)
 693			}
 694			return r.sendEmptyOrError(m, r.copyFile(fromFs, fromPath, toFs, toPath))
 695		}
 696	}
 697}
 698
 699func (r *runtime) deleteFile(fromFs, filename string) error {
 700	r.logger.Debug("delete", logger.NewLoggerPair("file", filename), logger.NewLoggerPair("fs", fromFs))
 701	switch fromFs {
 702	case "worktree":
 703		if ok, err := checkDelete(r.pluginRun.write.git, filename); !ok {
 704			r.logger.Error("plugin can't delete in git", err, logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename))
 705			return err
 706		}
 707		return r.repoWriter.Remove(filename)
 708	case "webcontent":
 709		if ok, err := checkDelete(r.pluginRun.write.web, filename); !ok {
 710			r.logger.Error("plugin can't delete in web", err, logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename))
 711			return err
 712		}
 713		fullPath := r.repo.PathDataWeb(filename)
 714		return os.Remove(fullPath)
 715	case "cache":
 716		fullPath := filepath.Join(r.manager.conf.PathCacheProject(r.repo.Name(), r.plugin.Name), filename)
 717		return os.Remove(fullPath)
 718	}
 719	return fmt.Errorf("unknown fs %s", fromFs)
 720}
 721
 722func (r *runtime) DeleteFileBuilder(forAS bool) interface{} {
 723	if !forAS {
 724		return func(_ context.Context, m api.Module, fromFsPtr, fromFsSize, fromPathPtr, fromGlobPathSize uint32) uint64 {
 725			fromFs, err := r.readString(m, "MoveFile fromFs", fromFsPtr, fromFsSize)
 726			if err != nil {
 727				return r.sendEmptyOrError(m, err)
 728			}
 729			fromPath, err := r.readString(m, "MoveFile fromGlobPath", fromPathPtr, fromGlobPathSize)
 730			if err != nil {
 731				return r.sendEmptyOrError(m, err)
 732			}
 733			return r.sendEmptyOrError(m, r.deleteFile(fromFs, fromPath))
 734		}
 735	} else {
 736		return func(_ context.Context, m api.Module, fromFsPtr, fromPathPtr uint32) uint64 {
 737			fromFs, err := r.readASString(m, "MoveFileAS fromFs", fromFsPtr)
 738			if err != nil {
 739				return r.sendEmptyOrError(m, err)
 740			}
 741			fromPath, err := r.readASString(m, "MoveFileAS fromGlobPath", fromPathPtr)
 742			if err != nil {
 743				return r.sendEmptyOrError(m, err)
 744			}
 745			return r.sendEmptyOrError(m, r.deleteFile(fromFs, fromPath))
 746		}
 747	}
 748}
 749
 750func (r *runtime) moveFile(fromFs, fromPath, toFs, toPath string) error {
 751	r.logger.Debug("moveFile", logger.NewLoggerPair("file", fromPath), logger.NewLoggerPair("fs", fromFs))
 752	if err := r.copyFile(fromFs, fromPath, toFs, toPath); err != nil {
 753		return err
 754	}
 755	r.logger.Debug("moveFile 2", logger.NewLoggerPair("file", fromPath), logger.NewLoggerPair("fs", fromFs))
 756	return r.deleteFile(fromFs, fromPath)
 757}
 758
 759func (r *runtime) MoveFileBuilder(forAS bool) interface{} {
 760	if !forAS {
 761		return func(_ context.Context, m api.Module, fromFsPtr, fromFsSize, fromPathPtr, fromGlobPathSize, toFsPtr, toFsSize, toPathPtr, toPathSize uint32) uint64 {
 762			fromFs, err := r.readString(m, "MoveFile fromFs", fromFsPtr, fromFsSize)
 763			if err != nil {
 764				return r.sendEmptyOrError(m, err)
 765			}
 766			fromPath, err := r.readString(m, "MoveFile fromGlobPath", fromPathPtr, fromGlobPathSize)
 767			if err != nil {
 768				return r.sendEmptyOrError(m, err)
 769			}
 770			toFs, err := r.readString(m, "MoveFile toFs", toFsPtr, toFsSize)
 771			if err != nil {
 772				return r.sendEmptyOrError(m, err)
 773			}
 774			toPath, err := r.readString(m, "MoveFile toPath", toPathPtr, toPathSize)
 775			if err != nil {
 776				return r.sendEmptyOrError(m, err)
 777			}
 778			return r.sendEmptyOrError(m, r.moveFile(fromFs, fromPath, toFs, toPath))
 779		}
 780	} else {
 781		return func(_ context.Context, m api.Module, fromFsPtr, fromPathPtr, toFsPtr, toPathPtr uint32) uint64 {
 782			fromFs, err := r.readASString(m, "MoveFileAS fromFs", fromFsPtr)
 783			if err != nil {
 784				return r.sendEmptyOrError(m, err)
 785			}
 786			fromPath, err := r.readASString(m, "MoveFileAS fromGlobPath", fromPathPtr)
 787			if err != nil {
 788				return r.sendEmptyOrError(m, err)
 789			}
 790			toFs, err := r.readASString(m, "MoveFileAS toFs", toFsPtr)
 791			if err != nil {
 792				return r.sendEmptyOrError(m, err)
 793			}
 794			toPath, err := r.readASString(m, "MoveFileAS toPath", toPathPtr)
 795			if err != nil {
 796				return r.sendEmptyOrError(m, err)
 797			}
 798			return r.sendEmptyOrError(m, r.moveFile(fromFs, fromPath, toFs, toPath))
 799		}
 800	}
 801}
 802
 803func (r *runtime) CommitAllBuilder(forAS bool) interface{} {
 804	f := func(msg string, err error) {
 805		if err != nil {
 806			return
 807		}
 808		if h, err := r.repoWriter.CommitAll(msg, r.plugin.commiter); err != nil {
 809			r.logger.Error("commitAll can't commit", err)
 810			return
 811		} else {
 812			r.logger.Debug("Commit", logger.NewLoggerPair("message", msg), logger.NewLoggerPair("hash", h.String()))
 813			if r.commitHook != nil && !h.IsZero() {
 814				r.commitHook(h)
 815			}
 816		}
 817	}
 818	if !forAS {
 819		return func(_ context.Context, m api.Module, offset, byteCount uint32) {
 820			f(r.readString(m, "commitAll", offset, byteCount))
 821		}
 822	} else {
 823		return func(_ context.Context, m api.Module, offset uint32) {
 824			f(r.readASString(m, "commitAll", offset))
 825		}
 826	}
 827}
 828
 829func (r *runtime) DiffWithParentBuilder(forAS bool) interface{} {
 830	f := func(m api.Module, hash string, oldfile string, newfile string) uint64 {
 831		r.logger.Info("In diffWithParent before found ancestor")
 832		diffStr, err := r.repoWriter.GetDiff(plumbing.NewHash(string(hash)), string(oldfile), string(newfile))
 833		if err != nil {
 834			r.logger.Error("GetDiff", err)
 835			return 0
 836		}
 837		r.logger.Debug("In diffWithParent send diff", logger.NewLoggerPair("diff", diffStr))
 838		ptrSize, err := r.sendData(m, diffStr)
 839		if err != nil {
 840			r.logger.Error("can't sendData", err, logger.NewLoggerPair("message", diffStr))
 841			return 0
 842		}
 843		return ptrSize
 844	}
 845	if !forAS {
 846		return func(_ context.Context, m api.Module, hashPtr, hashSize, oldFilenamePtr, oldFilenameSize, newFilenamePtr, newFilenameSize uint32) uint64 {
 847			hash, err := r.readString(m, "DiffWithParent hash", hashPtr, hashSize)
 848			if err != nil {
 849				return 0
 850			}
 851			oldFilename, err := r.readString(m, "DiffWithParent oldFilename", oldFilenamePtr, oldFilenameSize)
 852			if err != nil {
 853				return 0
 854			}
 855			newFilename, err := r.readString(m, "DiffWithParent newFilename", newFilenamePtr, newFilenameSize)
 856			if err != nil {
 857				return 0
 858			}
 859			return f(m, hash, oldFilename, newFilename)
 860		}
 861	} else {
 862		return func(_ context.Context, m api.Module, hashPtr, oldFilenamePtr, newFilenamePtr uint32) uint64 {
 863			hash, err := r.readASString(m, "DiffWithParent hash", hashPtr)
 864			if err != nil {
 865				return 0
 866			}
 867			oldFilename, err := r.readASString(m, "DiffWithParent oldFilenamePtr", oldFilenamePtr)
 868			if err != nil {
 869				return 0
 870			}
 871			newFilename, err := r.readASString(m, "DiffWithParent newFilename", newFilenamePtr)
 872			if err != nil {
 873				return 0
 874			}
 875			return f(m, hash, oldFilename, newFilename)
 876		}
 877	}
 878}
 879
 880func (r *runtime) LogBuilder(forAS bool) interface{} {
 881	f := func(msg string, err error) {
 882		if err != nil {
 883			return
 884		}
 885		repoName := ""
 886		if r.repo != nil { //in forconf scenario their is no repo
 887			repoName = r.repo.Name()
 888		}
 889		r.logger.Debug(msg, logger.NewLoggerPair("repo", repoName), logger.NewLoggerPair("plugin", r.plugin.Name))
 890	}
 891	if !forAS {
 892		return func(_ context.Context, m api.Module, offset, byteCount uint32) {
 893			f(r.readString(m, "log", offset, byteCount))
 894		}
 895	} else {
 896		return func(_ context.Context, m api.Module, offset uint32) {
 897			f(r.readASString(m, "log", offset))
 898		}
 899	}
 900}
 901
 902func (r *runtime) LogErrorBuilder(forAS bool) interface{} {
 903	f := func(msg string, err string) {
 904		repoName := ""
 905		if r.repo != nil { //in forconf scenario their is no repo
 906			repoName = r.repo.Name()
 907		}
 908		r.logger.Error(msg, errors.New(err), logger.NewLoggerPair("repo", repoName), logger.NewLoggerPair("plugin", r.plugin.Name))
 909	}
 910	if !forAS {
 911		return func(_ context.Context, m api.Module, offset, byteCount, errPtr, errSize uint32) {
 912			msg, err := r.readString(m, "LogError msg", offset, byteCount)
 913			if err != nil {
 914				return
 915			}
 916			errMsg, err := r.readString(m, "LogError err", errPtr, errSize)
 917			if err != nil {
 918				return
 919			}
 920			f(msg, errMsg)
 921		}
 922	} else {
 923		return func(_ context.Context, m api.Module, offset, errPtr uint32) {
 924			msg, err := r.readASString(m, "LogError msg", offset)
 925			if err != nil {
 926				return
 927			}
 928			errMsg, err := r.readASString(m, "LogError err", errPtr)
 929			if err != nil {
 930				return
 931			}
 932			f(msg, errMsg)
 933		}
 934	}
 935}
 936
 937func (r *runtime) MergeBuilder(forAS bool) interface{} {
 938	f := func(from string, to string) {
 939		r.logger.Debug("try to merge", logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
 940		cmd, err := r.repoWriter.Merge(from, to, r.plugin.commiter, r.command.pusher)
 941		if err != nil {
 942			r.logger.Error("can't Merge", err, logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
 943		}
 944		if r.mergeHook != nil {
 945			r.mergeHook(cmd, r.command.pusher, to)
 946		}
 947	}
 948	if !forAS {
 949		return func(_ context.Context, m api.Module, fromPtr, fromSize, toPtr, toSize uint32) {
 950			from, err := r.readString(m, "Merge from", fromPtr, fromSize)
 951			if err != nil {
 952				return
 953			}
 954			to, err := r.readString(m, "Merge to", toPtr, toSize)
 955			if err != nil {
 956				return
 957			}
 958			f(from, to)
 959		}
 960	} else {
 961		return func(_ context.Context, m api.Module, fromPtr, toPtr uint32) {
 962			from, err := r.readASString(m, "Merge from", fromPtr)
 963			if err != nil {
 964				return
 965			}
 966			to, err := r.readASString(m, "Merge to", toPtr)
 967			if err != nil {
 968				return
 969			}
 970			f(from, to)
 971		}
 972	}
 973}
 974
 975func (r *runtime) GetCommitsBuilder(forAS bool) interface{} {
 976	f := func(m api.Module, from string, to string) uint64 {
 977		groups, err := user.LoadGroup(r.repo)
 978		if err != nil {
 979			r.logger.Error("can't load group", err)
 980			return 0
 981		}
 982		commits := []commitForDiffCommit{}
 983		if err := r.repoWriter.WalkCommit(plumbing.NewHash(to), plumbing.NewHash(from), func(c *object.Commit) error {
 984			r.logger.Debug("found commit", logger.NewLoggerPair("hash", c.Hash.String()), logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
 985			commits = append(commits, commitToCommitForDiff(c, nil, groups))
 986			return nil
 987		}); err != nil {
 988			r.logger.Error("can't WalkCommit", err, logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
 989		}
 990		r.logger.Debug("found commits", logger.NewLoggerPair("nb", len(commits)), logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
 991		j, err := Marshall(r.command.branch.Short(), commits)
 992		if err != nil {
 993			r.logger.Error("can't Marshall", err, logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
 994		}
 995		ptrSize, err := r.sendData(m, j)
 996		if err != nil {
 997			r.logger.Error("can't sendData", err, logger.NewLoggerPair("message", j))
 998			return 0
 999		}
1000		return ptrSize
1001	}
1002	if !forAS {
1003		return func(_ context.Context, m api.Module, fromPtr, fromSize, toPtr, toSize uint32) uint64 {
1004			from, err := r.readString(m, "Merge from", fromPtr, fromSize)
1005			if err != nil {
1006				return 0
1007			}
1008			to, err := r.readString(m, "Merge to", toPtr, toSize)
1009			if err != nil {
1010				return 0
1011			}
1012			return f(m, from, to)
1013		}
1014	} else {
1015		return func(_ context.Context, m api.Module, fromPtr, toPtr uint32) uint64 {
1016			from, err := r.readASString(m, "Merge from", fromPtr)
1017			if err != nil {
1018				return 0
1019			}
1020			to, err := r.readASString(m, "Merge to", toPtr)
1021			if err != nil {
1022				return 0
1023			}
1024			return f(m, from, to)
1025		}
1026	}
1027}
1028
1029func (r *runtime) ExecBuilder(forAS bool) interface{} {
1030	f := func(m api.Module, cmd string) uint64 {
1031		exec := pluginLib.Exec{}
1032		err := json.Unmarshal([]byte(cmd), &exec)
1033		if err != nil {
1034			r.logger.Error("can't exec bad format", err)
1035			return 0
1036		}
1037		if ok := checkExec(r.pluginRun.write.exec, exec); !ok {
1038			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))
1039			return 0
1040		}
1041		j, err := r.manager.execManager.Exec(r.repo, r.command.branch.Short(), r.plugin.Name, exec)
1042		if err != nil {
1043			r.logger.Error("can't exec", err)
1044			return 0
1045		}
1046		jjson, err := json.Marshal(j)
1047		if err != nil {
1048			r.logger.Error("can't marshal execStatus", err)
1049			return 0
1050		}
1051		ptrSize, err := r.sendData(m, string(jjson))
1052		if err != nil {
1053			r.logger.Error("can't sendData", err, logger.NewLoggerPair("message", j))
1054			return 0
1055		}
1056		return ptrSize
1057	}
1058	if !forAS {
1059		return func(_ context.Context, m api.Module, commandPtr, commandSize uint32) uint64 {
1060			cmd, err := r.readString(m, "exec cmd", commandPtr, commandSize)
1061			if err != nil {
1062				return 0
1063			}
1064			return f(m, cmd)
1065		}
1066	} else {
1067		return func(_ context.Context, m api.Module, commandPtr uint32) uint64 {
1068			cmd, err := r.readASString(m, "exec cmd", commandPtr)
1069			if err != nil {
1070				return 0
1071			}
1072			return f(m, cmd)
1073		}
1074	}
1075}
1076
1077func (r *runtime) ReportBuilder(forAS bool) interface{} {
1078	f := func(reportJson string) {
1079		if r.command == nil || r.commit == nil {
1080			r.logger.Error("can't report in conf stage", errors.New("report not allowed"), logger.NewLoggerPair("plugin", r.plugin.Name))
1081			return
1082		}
1083		report := pluginLib.ReportToGitroot{}
1084		err := json.Unmarshal([]byte(reportJson), &report)
1085		if err != nil {
1086			r.logger.Error("can't unmarshal reportJson bad format", err)
1087			return
1088		}
1089		re := pluginLib.Report{
1090			Level:      report.Level,
1091			Content:    report.Content,
1092			FromPlugin: r.plugin.Name,
1093			FromBranch: r.command.branch.Short(),
1094			FromCommit: r.commit.hash.String(),
1095		}
1096		reJson, err := json.Marshal(re)
1097		if err != nil {
1098			r.logger.Error("can't marshal reJson bad format", err)
1099			return
1100		}
1101		r.logger.Debug("report added", logger.NewLoggerPair("report", reJson))
1102		r.reports = append(r.reports, re)
1103	}
1104	if !forAS {
1105		return func(_ context.Context, m api.Module, reportPtr, reportSize uint32) {
1106			reportJson, err := r.readString(m, "report json", reportPtr, reportSize)
1107			if err != nil {
1108				return
1109			}
1110			f(reportJson)
1111		}
1112	} else {
1113		return func(_ context.Context, m api.Module, reportPtr uint32) {
1114			reportJson, err := r.readASString(m, "report json", reportPtr)
1115			if err != nil {
1116				return
1117			}
1118			f(reportJson)
1119		}
1120	}
1121}
1122
1123func (r *runtime) CanCallBuilder(forAS bool) interface{} {
1124	f := func(callJson string) uint32 {
1125		repo := "no repo in conf mode"
1126		if r.repo != nil {
1127			repo = r.repo.Name()
1128		}
1129		r.logger.Info("can call json", logger.NewLoggerPair("repo", repo), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("data", string(callJson)))
1130		call := pluginLib.Call{}
1131		err := json.Unmarshal([]byte(callJson), &call)
1132		if err != nil {
1133			r.logger.Error("can't unmarshal canCallJson bad format", err)
1134			return 0
1135		}
1136		canCall := slices.ContainsFunc(r.pluginRun.write.callFunc, func(cf pluginLib.PluginCallFuncRight) bool {
1137			return cf.PluginName == call.Plugin && cf.FuncName == call.Name
1138		})
1139		// TODO find a way to check call.Args
1140		if !canCall {
1141			r.logger.Info("can't canCall func", logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("callPlugin", call.Plugin), logger.NewLoggerPair("func", call.Name), logger.NewLoggerPair("authorized", r.pluginRun.write.callFunc))
1142			return 0
1143		}
1144		return 1
1145	}
1146	if !forAS {
1147		return func(_ context.Context, m api.Module, callPtr, callSize uint32) uint32 {
1148			callJson, err := r.readString(m, "call json", callPtr, callSize)
1149			if err != nil {
1150				r.logger.Error("can't readString canCall", err)
1151				return 0
1152			}
1153			return f(callJson)
1154		}
1155	} else {
1156		return func(_ context.Context, m api.Module, callPtr uint32) uint32 {
1157			callJson, err := r.readASString(m, "call json", callPtr)
1158			if err != nil {
1159				r.logger.Error("can't readASString canCall", err)
1160				return 0
1161			}
1162			return f(callJson)
1163		}
1164	}
1165}
1166
1167func (r *runtime) CallBuilder(forAS bool) interface{} {
1168	f := func(callJson string) (uint64, api.Module) {
1169		r.logger.Info("call json", logger.NewLoggerPair("repo", r.repo.Name()), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("data", string(callJson)))
1170		// TODO duplicated in previous (function canCall check rights)
1171		call := pluginLib.Call{}
1172		err := json.Unmarshal([]byte(callJson), &call)
1173		if err != nil {
1174			r.logger.Error("can't unmarshal callJson bad format", err)
1175			return 0, nil
1176		}
1177		canCall := slices.ContainsFunc(r.pluginRun.write.callFunc, func(cf pluginLib.PluginCallFuncRight) bool {
1178			return cf.PluginName == call.Plugin && cf.FuncName == call.Name
1179		})
1180		if !canCall {
1181			r.logger.Error("can't call func", errors.New("not authorized in plugins file"), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("callPlugin", call.Plugin), logger.NewLoggerPair("func", call.Name), logger.NewLoggerPair("authorized", r.pluginRun.write.callFunc))
1182			return 0, nil
1183		}
1184		r.logger.Debug("call usableFromDefaultBranch from CallBuilder", logger.NewLoggerPair("repo", r.repo.Name()))
1185		plugins, err := r.manager.usableFromDefaultBranch(r.ctx, r.repo.Name())
1186		if err != nil {
1187			r.logger.Error("can't get availables plugins", err)
1188			return 0, nil
1189		}
1190		for _, plugin := range plugins {
1191			if plugin.Name == call.Plugin {
1192				m, err := r.loadModule(r.ctx, plugin, nil) //TODO where to find fs?
1193				if err != nil {
1194					r.logger.Error("can't loadModule", err, logger.NewLoggerPair("plugin", plugin.Name))
1195					return 0, nil
1196				}
1197				funcToCall := m.ExportedFunction("call")
1198				malloc := m.ExportedFunction("gitrootAlloc")
1199				if malloc == nil {
1200					malloc = m.ExportedFunction("malloc")
1201				}
1202				callResPtrSize, err := r.writeMemoryAndCallWithRes(m, funcToCall, malloc, callJson)
1203				if err != nil {
1204					r.logger.Error("can't writeMemoryAndCallWithRes", err, logger.NewLoggerPair("plugin", plugin.Name))
1205					return 0, nil
1206				}
1207				return callResPtrSize, m
1208			}
1209		}
1210		r.logger.Error("plugin not found", err, logger.NewLoggerPair("plugin", call.Plugin), logger.NewLoggerPair("method", call.Name))
1211		return 0, nil
1212	}
1213	callResJson, _ := json.Marshal(pluginLib.CallRes{Err: "plugin or method not found"})
1214	if !forAS {
1215		return func(_ context.Context, m api.Module, callPtr, callSize uint32) uint64 {
1216			callJson, err := r.readString(m, "call json", callPtr, callSize)
1217			if err != nil {
1218				r.logger.Error("can't readString call", err)
1219				return 0
1220			}
1221			callResPtrSize, newModule := f(callJson)
1222			if callResPtrSize > 0 && newModule != nil {
1223				ptrCall := uint32(callResPtrSize >> 32)
1224				sizeCall := uint32(callResPtrSize)
1225				callResJsonStr, err := r.readString(newModule, "can't read string in call", ptrCall, sizeCall)
1226				if err != nil {
1227					r.logger.Error("can't readString callRes", err)
1228					return 0
1229				}
1230				callResJson = []byte(callResJsonStr)
1231			}
1232			ptrSize, err := r.sendData(m, string(callResJson))
1233			if err != nil {
1234				r.logger.Error("can't sendData callRes", err)
1235				return 0
1236			}
1237			return ptrSize
1238		}
1239	} else {
1240		return func(_ context.Context, m api.Module, callPtr uint32) uint64 {
1241			callJson, err := r.readASString(m, "call json", callPtr)
1242			if err != nil {
1243				r.logger.Error("can't readASString call", err)
1244				return 0
1245			}
1246			callResPtrSize, newModule := f(callJson)
1247			if callResPtrSize > 0 && newModule != nil {
1248				callResJsonStr, err := r.readASString(newModule, "can't read asstring in call", uint32(callResPtrSize))
1249				if err != nil {
1250					r.logger.Error("can't readASString callRes", err)
1251					return 0
1252				}
1253				callResJson = []byte(callResJsonStr)
1254			}
1255			ptrSize, err := r.sendData(m, string(callResJson))
1256			if err != nil {
1257				r.logger.Error("can't sendData callRes", err)
1258				return 0
1259			}
1260			return ptrSize
1261		}
1262	}
1263}
1264
1265func (r *runtime) Close() error {
1266	return r.wazRun.Close(r.ctx)
1267}
1268
1269func (r *runtime) sendData(module api.Module, message string) (ptrSizeToSend uint64, err error) {
1270	malloc := module.ExportedFunction("gitrootAlloc")
1271	if malloc == nil {
1272		malloc = module.ExportedFunction("malloc")
1273	}
1274
1275	s := uint64(len(message))
1276
1277	results, err := malloc.Call(r.ctx, s)
1278	if err != nil {
1279		return 0, oops.Wrapf(err, "can't malloc memory")
1280	}
1281	ptrA := results[0]
1282
1283	// The pointer is a linear memory offset, which is where we write the name.
1284	if !module.Memory().WriteString(uint32(ptrA), message) {
1285		return 0, oops.Errorf("can't write memory")
1286	}
1287
1288	r.memoryToGarbage = append(r.memoryToGarbage, memoryGarbage{module: module, ptrSize: ptrSize{ptr: ptrA, size: s}})
1289
1290	return (ptrA << uint64(32)) | s, nil
1291}
1292
1293func (r *runtime) sendEmptyOrError(module api.Module, err error) uint64 {
1294	if err == nil {
1295		r.logger.Debug("no error to send")
1296		return 0
1297	}
1298	ptrSize, err := r.sendData(module, err.Error())
1299	if err != nil {
1300		r.logger.Error("can't sendError", err, logger.NewLoggerPair("originalErr", err.Error()))
1301		return 0
1302	}
1303	return ptrSize
1304}
1305
1306func (r *runtime) readString(m api.Module, errorMsg string, ptr uint32, size uint32) (string, error) {
1307	mem := m.Memory()
1308	if mem == nil {
1309		err := errors.New("memory not exist")
1310		r.logger.Error(errorMsg, err, logger.NewLoggerPair("ptr", ptr), logger.NewLoggerPair("size", size))
1311		return "", err
1312	}
1313	txt, ok := mem.Read(ptr, size)
1314	if !ok {
1315		err := errors.New("memory read fail")
1316		r.logger.Error(errorMsg, err, logger.NewLoggerPair("ptr", ptr), logger.NewLoggerPair("size", size))
1317		return "", err
1318	}
1319	return string(txt), nil
1320}
1321
1322func (r *runtime) readASString(m api.Module, errorMsg string, offset uint32) (string, error) {
1323	// Length is four bytes before pointer.
1324	byteCount, ok := m.Memory().ReadUint32Le(offset - 4)
1325	if !ok || byteCount%2 != 0 {
1326		err := errors.New("Memory.ReadUint32Le fail")
1327		r.logger.Error(errorMsg, err, logger.NewLoggerPair("byteCount", byteCount), logger.NewLoggerPair("offset", offset))
1328		return "", err
1329	}
1330	buf, ok := m.Memory().Read(offset, byteCount)
1331	if !ok {
1332		err := errors.New("Memory.Read fail")
1333		r.logger.Error(errorMsg, err, logger.NewLoggerPair("offset", offset), logger.NewLoggerPair("byteCount", byteCount))
1334		return "", err
1335	}
1336	u16s := make([]uint16, len(buf)/2)
1337
1338	lb := len(buf)
1339	for i := 0; i < lb; i += 2 {
1340		u16s[i/2] = uint16(buf[i]) + (uint16(buf[i+1]) << 8)
1341	}
1342	return string(utf16.Decode(u16s)), nil
1343}
1344
1345type ptrSize struct {
1346	ptr  uint64
1347	size uint64
1348}
1349
1350func (r *runtime) free(module api.Module, ptrSize []ptrSize) error {
1351	free := module.ExportedFunction("gitrootFree")
1352	if free == nil {
1353		free = module.ExportedFunction("free")
1354	}
1355
1356	if free != nil && len(free.Definition().ParamTypes()) == 1 {
1357		for _, p := range ptrSize {
1358			_, err := free.Call(r.ctx, p.ptr)
1359			if err != nil {
1360				r.logger.Error("can't free pluginConf 1 param", err, logger.NewLoggerPair("plugin", module.Name()))
1361			}
1362		}
1363	} else if free != nil {
1364		for _, p := range ptrSize {
1365			_, err := free.Call(r.ctx, p.ptr, p.size)
1366			if err != nil {
1367				r.logger.Error("can't free pluginConf", err, logger.NewLoggerPair("plugin", module.Name()))
1368			}
1369		}
1370	}
1371
1372	return nil
1373}