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 "encoding/json"
10 "fmt"
11 "io/fs"
12 "slices"
13
14 "github.com/go-git/go-git/v6/plumbing"
15 "github.com/samber/oops"
16 "github.com/tetratelabs/wazero/api"
17 "gitroot.dev/libs/golang/plugin/model"
18 pluginLib "gitroot.dev/libs/golang/plugin/model"
19 "gitroot.dev/server/logger"
20 "gitroot.dev/server/repository"
21 "gitroot.dev/server/user"
22)
23
24func (r *runtime) worktree(ctx context.Context, repo *repository.GitRootRepository, repoWriter *repository.GitRootRepositoryWrite, plugins []Plugin, cmd CommandForDiff) ([]CommandForDiff, error) {
25 r.repo = repo
26 r.repoWriter = repoWriter
27 r.command = &cmd
28 r.commit = nil
29
30 toRunLater := []CommandForDiff{}
31
32 filepaths := []string{}
33 fs.WalkDir(repoWriter.ToFs(ctx), ".", 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 toRunLater, err
42 }
43 slices.Reverse(commands)
44
45 r.commitHook = func(hash plumbing.Hash) {
46 lastCom, err := repoWriter.GetLastCommit(hash)
47 if err != nil {
48 r.logger.Error("can't GetLastCommit in runtime worktree", err)
49 return
50 }
51 newCmd, err := CommandForDiffFromCommitCmd(ctx, r.plugin.commiter.SimpleUser, lastCom, r.command.branch)
52 if err != nil {
53 r.logger.Error("can't CommandForDiffFromCommitCmd in runtime worktree", err)
54 return
55 }
56 toRunLater = append(toRunLater, newCmd)
57 }
58 defer func() {
59 r.repo = nil
60 r.repoWriter = nil
61 r.command = nil
62 r.commitHook = nil
63 r.commit = nil
64 }()
65
66 groups, err := user.LoadGroup(r.repo, r.command.branch)
67 if err != nil {
68 return toRunLater, oops.Wrapf(err, "can't load group")
69 }
70
71 var m api.Module
72 for _, plugin := range plugins {
73 r.plugin = plugin
74 timerStop := r.logger.Time(fmt.Sprintf("Timer %s", plugin.Log()))
75
76 r.logger.Debug("start plugin worktree", logger.NewLoggerPair("name", plugin.Log()))
77 m, _, err = r.loadModule(ctx, plugin)
78 if err != nil {
79 r.logger.Error("loadModule for worktree error", err, logger.NewLoggerPair("name", plugin.Log()))
80 continue
81 }
82 l := logger.NewLogger(logger.WASM)
83 l.Debug("memory before worktree", logger.NewLoggerPair("size", m.Memory().Size()), logger.NewLoggerPair("plugin", plugin.Log()))
84 startCommit := m.ExportedFunction("startCommit")
85 endCommit := m.ExportedFunction("endCommit")
86 addFile := m.ExportedFunction("addFile")
87 malloc := m.ExportedFunction("gitrootAlloc")
88 if malloc == nil {
89 malloc = m.ExportedFunction("malloc")
90 }
91
92 for _, pluginRun := range plugin.Run {
93 isAuthorized := checkBranch(pluginRun, r.command.branch)
94 if !isAuthorized {
95 continue
96 }
97 r.pluginRun = pluginRun
98 if init := m.ExportedFunction("init"); init != nil {
99 arg, err := pluginRun.Marshal()
100 if err != nil {
101 r.logger.Error("can't marshall pluginRun", err, logger.NewLoggerPair("name", plugin.Log()))
102 continue
103 }
104 r.logger.Debug("init plugin worktree", logger.NewLoggerPair("name", plugin.Log()), logger.NewLoggerPair("arg", arg))
105 if err := r.writeMemoryAndCall(m, init, malloc, repo.Name(), string(model.InitKindConfHasChange), string(arg)); err != nil {
106 r.logger.Error("can't writeMemoryAndCall init", err, logger.NewLoggerPair("name", plugin.Log()))
107 continue
108 }
109 }
110
111 if slices.Contains(pluginRun.When, pluginLib.PluginRunWhenAdd) {
112 for _, lastCommit := range commands {
113 firstCall := true
114 hasBeenCalled := false
115 com := commitToCommitForDiff(lastCommit.Commit, []pluginLib.File{}, groups)
116 r.commit = &com
117 comMarshalled, err := MarshallOne(r.command.branch.Short(), com)
118 if err != nil {
119 r.logger.Error("can't MarshallOne commit", err, logger.NewLoggerPair("name", plugin.Log()))
120 continue
121 }
122 for _, f := range lastCommit.Filepath {
123 if pluginRun.glob.Match(f.Path) {
124 if startCommit != nil && firstCall {
125 firstCall = false
126 if err := r.writeMemoryAndCall(m, startCommit, malloc, comMarshalled); err != nil {
127 r.logger.Error("can't writeMemoryAndCall startCommit", err, logger.NewLoggerPair("name", plugin.Log()))
128 continue
129 }
130 }
131 if addFile != nil {
132 jsonFile, err := json.Marshal(f)
133 if err != nil {
134 r.logger.Error("can marshal file", err, logger.NewLoggerPair("branch", r.command.branch.Short()), logger.NewLoggerPair("plugin", r.plugin.Log()))
135 continue
136 }
137 if err := r.writeMemoryAndCall(m, addFile, malloc, string(jsonFile)); err != nil {
138 r.logger.Error("can't writeMemoryAndCall addFile", err, logger.NewLoggerPair("name", plugin.Log()))
139 continue
140 }
141 hasBeenCalled = true
142 }
143 }
144 }
145 if hasBeenCalled && endCommit != nil {
146 if err := r.writeMemoryAndCall(m, endCommit, malloc, comMarshalled); err != nil {
147 r.logger.Error("can't writeMemoryAndCall endCommit", err, logger.NewLoggerPair("name", plugin.Log()))
148 continue
149 }
150 }
151 }
152 }
153
154 if finish := m.ExportedFunction("finish"); finish != nil {
155 if _, err := finish.Call(ctx); err != nil {
156 r.logger.Error("can't call finish", err, logger.NewLoggerPair("name", plugin.Log()))
157 continue
158 }
159 }
160 }
161
162 l.Debug("memory after worktree", logger.NewLoggerPair("size", m.Memory().Size()), logger.NewLoggerPair("plugin", plugin.Log()))
163 r.logger.Debug("finish plugin conf", logger.NewLoggerPair("name", plugin.Log()))
164 timerStop()
165 }
166
167 return toRunLater, nil
168}