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	"fmt"
 10	"io/fs"
 11	"os"
 12	"slices"
 13	"strings"
 14	"time"
 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"
 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
 43	r.commitHook = func(hash plumbing.Hash) {
 44		command, err := repoWriter.GetLastCommit(hash)
 45		if err != nil {
 46			r.logger.Error("can't GetLastCommit", err)
 47			return
 48		}
 49		commands = append(commands, command)
 50	}
 51	defer func() {
 52		r.commitHook = nil
 53	}()
 54
 55	for _, plugin := range plugins {
 56		r.fsCache.Update(r.manager.conf.Cache(repo.Name(), plugin.Name))
 57		r.plugin = plugin
 58		timerStop := r.logger.Time(fmt.Sprintf("Timer %s", plugin.Name))
 59
 60		r.logger.Debug("start plugin worktree", logger.NewLoggerPair("name", plugin.Name))
 61		m := r.wazRun.Module(plugin.Name)
 62		if m == nil {
 63			r.logger.Debug("instantiate plugin conf", logger.NewLoggerPair("name", plugin.Name))
 64			config := wazero.NewModuleConfig().
 65				WithStdout(os.Stdout).WithStderr(os.Stderr).
 66				WithFSConfig(wazero.NewFSConfig().
 67					WithFSMount(r.fsWorktree, "worktree").
 68					WithFSMount(r.fsWebcontent, "webcontent").
 69					WithFSMount(r.fsCache, "cache")).
 70				WithName(plugin.Name).
 71				WithStartFunctions("_initialize", "install")
 72			mod, err := r.wazRun.InstantiateWithConfig(ctx, plugin.content, config)
 73			if err != nil {
 74				if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
 75					fmt.Fprintf(os.Stderr, "exit_code: %d\n", exitErr.ExitCode())
 76					return err
 77				} else if !ok {
 78					return err
 79				}
 80			}
 81			m = mod
 82		} else {
 83			r.logger.Debug("already exist conf", logger.NewLoggerPair("name", plugin.Name))
 84		}
 85		l := logger.NewLogger(logger.WASM)
 86		l.Debug("memory before worktree", logger.NewLoggerPair("size", m.Memory().Size()), logger.NewLoggerPair("plugin", plugin.Name))
 87		startCommit := m.ExportedFunction("startCommit")
 88		endCommit := m.ExportedFunction("endCommit")
 89		addFile := m.ExportedFunction("addFile")
 90		malloc := m.ExportedFunction("malloc")
 91		free := m.ExportedFunction("free")
 92
 93		for _, pluginRun := range plugin.Run {
 94			isAuthorized := checkBranch(pluginRun, r.command.branch)
 95			if !isAuthorized {
 96				continue
 97			}
 98			r.pluginRun = pluginRun
 99			if init := m.ExportedFunction("init"); init != nil {
100				arg, err := pluginRun.Marshal()
101				if err != nil {
102					return err
103				}
104				r.logger.Debug("init plugin worktree", logger.NewLoggerPair("name", plugin.Name), logger.NewLoggerPair("arg", arg))
105				if err := writeMemoryAndCall(ctx, m, init, malloc, free, repo.Name(), "true", string(arg)); err != nil {
106					return err
107				}
108			}
109
110			if slices.Contains(pluginRun.When, pluginLib.PluginRunWhenAdd) {
111				for _, lastCommit := range commands {
112					firstCall := true
113					hasBeenCalled := false
114					for _, path := range lastCommit.Filepath {
115						if pluginRun.glob.Match(path) {
116							if startCommit != nil && firstCall {
117								firstCall = false
118								parent, _ := lastCommit.Commit.Parent(0) // TODO what parent?
119								com := commitToCommitForDiff(lastCommit.Commit, parent, []commitForDiffFile{})
120								if err := writeMemoryAndCall(ctx, m, startCommit, malloc, free, r.command.branch.Short(), com.hash.String(), com.message, com.date.Format(time.RFC3339)); err != nil {
121									return err
122								}
123							}
124							if err := writeMemoryAndCall(ctx, m, addFile, malloc, free, strings.TrimPrefix(path, "/")); err != nil {
125								return err
126							}
127							hasBeenCalled = true
128						}
129					}
130					if hasBeenCalled {
131						if err := writeMemoryAndCall(ctx, m, endCommit, malloc, free, r.command.branch.Short(), lastCommit.Commit.Hash.String(), lastCommit.Commit.Message); err != nil {
132							return err
133						}
134					}
135				}
136			}
137
138			if finish := m.ExportedFunction("finish"); finish != nil {
139				if _, err := finish.Call(ctx); err != nil {
140					return err
141				}
142			}
143		}
144
145		l.Debug("memory after worktree", logger.NewLoggerPair("size", m.Memory().Size()), logger.NewLoggerPair("plugin", plugin.Name))
146		r.logger.Debug("finish plugin conf", logger.NewLoggerPair("name", plugin.Name))
147		timerStop()
148	}
149
150	return nil
151}