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	"time"
 16
 17	"github.com/go-git/go-git/v5/plumbing"
 18	"github.com/tetratelabs/wazero"
 19	"github.com/tetratelabs/wazero/sys"
 20	pluginLib "gitroot.dev/libs/golang/plugin"
 21	"gitroot.dev/server/logger"
 22	"gitroot.dev/server/repository"
 23)
 24
 25func (r *runtime) worktree(ctx context.Context, repo *repository.GitRootRepository, repoWriter *repository.GitRootRepositoryWrite, plugins []Plugin, cmds []CommandForDiff) error {
 26	r.fsWorktree.Update(repoWriter.ToFs(ctx))
 27	r.fsWebcontent.Update(r.manager.conf.DataWeb(repo.Name()))
 28	r.repo = repo
 29	r.repoWriter = repoWriter
 30	r.command = &cmds[len(cmds)-1] // TODO should not be last cmd see ./manager.go line 65 todo
 31
 32	filepaths := []string{}
 33	fs.WalkDir(r.fsWorktree, ".", func(path string, d fs.DirEntry, err error) error {
 34		if !d.IsDir() {
 35			filepaths = append(filepaths, path)
 36		}
 37		return nil
 38	})
 39	commands, err := repo.GetLastCommits(filepaths)
 40	if err != nil {
 41		return err
 42	}
 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.Name)
 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.Name).
 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					parent, _ := lastCommit.Commit.Parent(0) // TODO what parent?
119					com := commitToCommitForDiff(lastCommit.Commit, parent, []commitForDiffFile{})
120					for _, path := range lastCommit.Filepath {
121						if pluginRun.glob.Match(path) {
122							if startCommit != nil && firstCall {
123								firstCall = false
124								if err := writeMemoryAndCall(ctx, m, startCommit, malloc, free, r.command.branch.Short(), com.hash.String(), com.message, com.date.Format(time.RFC3339), com.committer); err != nil {
125									return err
126								}
127							}
128							if addFile != nil {
129								if err := writeMemoryAndCall(ctx, m, addFile, malloc, free, strings.TrimPrefix(path, "/")); err != nil {
130									return err
131								}
132								hasBeenCalled = true
133							}
134						}
135					}
136					if hasBeenCalled && endCommit != nil {
137						if err := writeMemoryAndCall(ctx, m, endCommit, malloc, free, r.command.branch.Short(), com.hash.String(), com.message, com.date.Format(time.RFC3339), com.committer); err != nil {
138							return err
139						}
140					}
141				}
142			}
143
144			if finish := m.ExportedFunction("finish"); finish != nil {
145				if _, err := finish.Call(ctx); err != nil {
146					return err
147				}
148			}
149		}
150
151		l.Debug("memory after worktree", logger.NewLoggerPair("size", m.Memory().Size()), logger.NewLoggerPair("plugin", plugin.Name))
152		r.logger.Debug("finish plugin conf", logger.NewLoggerPair("name", plugin.Name))
153		timerStop()
154	}
155
156	return nil
157}