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 "slices"
10 "time"
11
12 "github.com/samber/oops"
13 "github.com/tetratelabs/wazero/api"
14 pluginLib "gitroot.dev/libs/golang/plugin"
15 "gitroot.dev/server/logger"
16 "gitroot.dev/server/repository"
17)
18
19type callPlugin struct {
20 manager *Manager
21 plugin Plugin
22 repo *repository.GitRootRepository
23 repoWriter *repository.GitRootRepositoryWrite
24 module api.Module
25 logger *logger.Logger
26}
27
28func (c callPlugin) callPluginForDiff(ctx context.Context, r *runtime, commands []CommandForDiff) error {
29 startCommit := c.module.ExportedFunction("startCommit")
30 addFile := c.module.ExportedFunction("addFile")
31 modFile := c.module.ExportedFunction("modFile")
32 delFile := c.module.ExportedFunction("delFile")
33 endCommit := c.module.ExportedFunction("endCommit")
34 malloc := c.module.ExportedFunction("malloc")
35 free := c.module.ExportedFunction("free")
36
37 currentBranch, err := c.repo.CurrentBranch()
38 if err != nil {
39 return oops.Wrapf(err, "can't get CurrentBranch")
40 }
41
42 for _, cmd := range commands {
43 r.command = &cmd
44 for _, pluginRun := range r.plugin.Run {
45 r.pluginRun = pluginRun
46 callOnAdd := addFile != nil && slices.Contains(pluginRun.When, pluginLib.PluginRunWhenAdd)
47 callOnMod := modFile != nil && slices.Contains(pluginRun.When, pluginLib.PluginRunWhenMod)
48 callOnDel := delFile != nil && slices.Contains(pluginRun.When, pluginLib.PluginRunWhenDel)
49 isAuthorized := checkBranch(pluginRun, cmd.branch)
50 if !isAuthorized {
51 continue
52 }
53
54 if cmd.branchAction == commitForDiffActionDel {
55 c.logger.Info("delete branch", logger.NewLoggerPair("branch", cmd.branch))
56 continue
57 }
58
59 if err := c.repoWriter.Checkout(cmd.branch); err != nil {
60 c.logger.Error("can't Checkout", err, logger.NewLoggerPair("branch", cmd.branch))
61 continue
62 }
63
64 if init := c.module.ExportedFunction("init"); init != nil {
65 arg, err := pluginRun.Marshal()
66 if err != nil {
67 return err
68 }
69 c.logger.Debug("init plugin", logger.NewLoggerPair("name", c.plugin.Name), logger.NewLoggerPair("arg", arg))
70 if err := writeMemoryAndCall(ctx, c.module, init, malloc, free, c.repo.Name(), "false", string(arg)); err != nil {
71 return err
72 }
73 }
74
75 for _, com := range cmd.commits {
76 if startCommit != nil {
77 c.logger.Debug("startCommit", logger.NewLoggerPair("branch", cmd.branch.Short()), logger.NewLoggerPair("hash", com.hash.String()), logger.NewLoggerPair("message", com.message), logger.NewLoggerPair("date", com.date.Format(time.RFC3339)))
78 if err := writeMemoryAndCall(ctx, c.module, startCommit, malloc, free, cmd.branch.Short(), com.hash.String(), com.message, com.date.Format(time.RFC3339), com.committer); err != nil {
79 return err
80 }
81 }
82
83 for _, f := range com.files {
84 if f.action == commitForDiffActionAdd && callOnAdd {
85 c.logger.Debug("add", logger.NewLoggerPair("confPath", pluginRun.Path), logger.NewLoggerPair("currentPath", f.path))
86 if pluginRun.glob.Match(f.path) {
87 // creation
88 writeMemoryAndCall(ctx, c.module, addFile, malloc, free, f.path)
89 }
90 } else {
91 if pluginRun.glob.Match(f.path) {
92 if f.action == commitForDiffActionDel && callOnDel {
93 // deletion
94 writeMemoryAndCall(ctx, c.module, delFile, malloc, free, f.path)
95 } else if f.action == commitForDiffActionMod && callOnMod {
96 //modification
97 writeMemoryAndCall(ctx, c.module, modFile, malloc, free, f.oldPath, f.path)
98 }
99 }
100 }
101 }
102
103 if endCommit != nil {
104 if err := writeMemoryAndCall(ctx, c.module, endCommit, malloc, free, cmd.branch.Short(), com.hash.String(), com.message, com.date.Format(time.RFC3339), com.committer); err != nil {
105 return err
106 }
107 }
108 }
109
110 if finish := c.module.ExportedFunction("finish"); finish != nil {
111 if _, err := finish.Call(ctx); err != nil {
112 return err
113 }
114 }
115 }
116 }
117
118 if err := c.repoWriter.Checkout(currentBranch); err != nil {
119 c.logger.Error("can't Checkout", err, logger.NewLoggerPair("branch", currentBranch.Short()))
120 return err
121 }
122 return nil
123}
124
125func writeMemoryAndCall(ctx context.Context, module api.Module, toCall api.Function, malloc api.Function, free api.Function, message ...string) error {
126 params := make([]uint64, 0)
127 for _, m := range message {
128 size := uint64(len(m))
129
130 results, err := malloc.Call(ctx, size)
131 if err != nil {
132 return oops.Wrapf(err, "can't malloc memory for %s with %s", toCall.Definition().Name(), m)
133 }
134 ptr := results[0]
135
136 // The pointer is a linear memory offset, which is where we write the name.
137 if !module.Memory().Write(uint32(ptr), []byte(m)) {
138 return oops.Wrapf(err, "can't write memory")
139 }
140
141 params = append(params, ptr, size)
142 }
143
144 defer func() {
145 for i, d := range params {
146 if i%2 == 0 {
147 free.Call(ctx, d)
148 }
149 }
150 }()
151
152 if _, err := toCall.Call(ctx, params...); err != nil {
153 return err
154 }
155
156 return nil
157}