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	"context"
  9	"crypto/rand"
 10	"fmt"
 11	"io/fs"
 12	"os"
 13	"slices"
 14	"strings"
 15
 16	"github.com/go-git/go-git/v5/plumbing"
 17	"github.com/tetratelabs/wazero"
 18	"github.com/tetratelabs/wazero/sys"
 19	pluginLib "gitroot.dev/libs/golang/plugin/model"
 20	"gitroot.dev/server/logger"
 21	"gitroot.dev/server/repository"
 22)
 23
 24func (r *runtime) worktree(ctx context.Context, repo *repository.GitRootRepository, repoWriter *repository.GitRootRepositoryWrite, plugins []Plugin, cmds []CommandForDiff) error {
 25	r.fsWorktree.Update(repoWriter.ToFs(ctx))
 26	r.fsWebcontent.Update(r.manager.conf.DataWeb(repo.Name()))
 27	r.repo = repo
 28	r.repoWriter = repoWriter
 29	r.command = &cmds[len(cmds)-1] // TODO should not be last cmd see ./manager.go line 65 todo
 30
 31	filepaths := []string{}
 32	fs.WalkDir(r.fsWorktree, ".", func(path string, d fs.DirEntry, err error) error {
 33		if !d.IsDir() {
 34			filepaths = append(filepaths, path)
 35		}
 36		return nil
 37	})
 38	commands, err := repo.GetLastCommits(filepaths)
 39	if err != nil {
 40		return err
 41	}
 42	slices.Reverse(commands)
 43
 44	r.commitHook = func(hash plumbing.Hash) {
 45		command, err := repoWriter.GetLastCommit(hash)
 46		if err != nil {
 47			r.logger.Error("can't GetLastCommit", err)
 48			return
 49		}
 50		commands = append(commands, command)
 51	}
 52	defer func() {
 53		r.commitHook = nil
 54	}()
 55
 56	for _, plugin := range plugins {
 57		r.fsCache.Update(r.manager.conf.Cache(repo.Name(), plugin.Name))
 58		r.plugin = plugin
 59		timerStop := r.logger.Time(fmt.Sprintf("Timer %s", plugin.Name))
 60
 61		r.logger.Debug("start plugin worktree", logger.NewLoggerPair("name", plugin.Name))
 62		m := r.wazRun.Module(plugin.uuid())
 63		if m == nil {
 64			r.logger.Debug("instantiate plugin conf", logger.NewLoggerPair("name", plugin.Name))
 65			config := wazero.NewModuleConfig().
 66				WithStdout(os.Stdout).WithStderr(os.Stderr).
 67				WithSysWalltime().
 68				WithSysNanotime().
 69				WithRandSource(rand.Reader).
 70				WithFSConfig(wazero.NewFSConfig().
 71					WithFSMount(r.fsWorktree, "worktree").
 72					WithFSMount(r.fsWebcontent, "webcontent").
 73					WithFSMount(r.fsCache, "cache")).
 74				WithName(plugin.uuid()).
 75				WithStartFunctions("_initialize", "install")
 76			mod, err := r.wazRun.InstantiateWithConfig(ctx, plugin.content, config)
 77			if err != nil {
 78				if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
 79					fmt.Fprintf(os.Stderr, "exit_code: %d\n", exitErr.ExitCode())
 80					return err
 81				} else if !ok {
 82					return err
 83				}
 84			}
 85			m = mod
 86		} else {
 87			r.logger.Debug("already exist conf", logger.NewLoggerPair("name", plugin.Name))
 88		}
 89		l := logger.NewLogger(logger.WASM)
 90		l.Debug("memory before worktree", logger.NewLoggerPair("size", m.Memory().Size()), logger.NewLoggerPair("plugin", plugin.Name))
 91		startCommit := m.ExportedFunction("startCommit")
 92		endCommit := m.ExportedFunction("endCommit")
 93		addFile := m.ExportedFunction("addFile")
 94		malloc := m.ExportedFunction("malloc")
 95		free := m.ExportedFunction("free")
 96
 97		for _, pluginRun := range plugin.Run {
 98			isAuthorized := checkBranch(pluginRun, r.command.branch)
 99			if !isAuthorized {
100				continue
101			}
102			r.pluginRun = pluginRun
103			if init := m.ExportedFunction("init"); init != nil {
104				arg, err := pluginRun.Marshal()
105				if err != nil {
106					return err
107				}
108				r.logger.Debug("init plugin worktree", logger.NewLoggerPair("name", plugin.Name), logger.NewLoggerPair("arg", arg))
109				if err := writeMemoryAndCall(ctx, m, init, malloc, free, repo.Name(), "true", string(arg)); err != nil {
110					return err
111				}
112			}
113
114			if slices.Contains(pluginRun.When, pluginLib.PluginRunWhenAdd) {
115				for _, lastCommit := range commands {
116					firstCall := true
117					hasBeenCalled := false
118					com := commitToCommitForDiff(lastCommit.Commit, []commitForDiffFile{})
119					comMarshalled, err := MarshallOne(r.command.branch.Short(), com)
120					if err != nil {
121						return err
122					}
123					for _, path := range lastCommit.Filepath {
124						if pluginRun.glob.Match(path) {
125							if startCommit != nil && firstCall {
126								firstCall = false
127								if err := writeMemoryAndCall(ctx, m, startCommit, malloc, free, comMarshalled); err != nil {
128									return err
129								}
130							}
131							if addFile != nil {
132								if err := writeMemoryAndCall(ctx, m, addFile, malloc, free, strings.TrimPrefix(path, "/")); err != nil {
133									return err
134								}
135								hasBeenCalled = true
136							}
137						}
138					}
139					if hasBeenCalled && endCommit != nil {
140						if err := writeMemoryAndCall(ctx, m, endCommit, malloc, free, comMarshalled); err != nil {
141							return err
142						}
143					}
144				}
145			}
146
147			if finish := m.ExportedFunction("finish"); finish != nil {
148				if _, err := finish.Call(ctx); err != nil {
149					return err
150				}
151			}
152		}
153
154		l.Debug("memory after worktree", logger.NewLoggerPair("size", m.Memory().Size()), logger.NewLoggerPair("plugin", plugin.Name))
155		r.logger.Debug("finish plugin conf", logger.NewLoggerPair("name", plugin.Name))
156		timerStop()
157	}
158
159	return nil
160}