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}