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