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}