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