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}