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