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