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/model"
 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("gitrootAlloc")
 35	if malloc == nil {
 36		malloc = c.module.ExportedFunction("malloc")
 37	}
 38	free := c.module.ExportedFunction("gitrootFree")
 39	if free == nil {
 40		free = c.module.ExportedFunction("free")
 41	}
 42
 43	currentBranch, err := c.repo.CurrentBranch()
 44	if err != nil {
 45		return oops.Wrapf(err, "can't get CurrentBranch")
 46	}
 47
 48	for _, cmd := range commands {
 49		r.command = &cmd
 50		for _, pluginRun := range r.plugin.Run {
 51			r.pluginRun = pluginRun
 52			callOnAdd := addFile != nil && slices.Contains(pluginRun.When, pluginLib.PluginRunWhenAdd)
 53			callOnMod := modFile != nil && slices.Contains(pluginRun.When, pluginLib.PluginRunWhenMod)
 54			callOnDel := delFile != nil && slices.Contains(pluginRun.When, pluginLib.PluginRunWhenDel)
 55			isAuthorized := checkBranch(pluginRun, cmd.branch)
 56			if !isAuthorized {
 57				continue
 58			}
 59
 60			if cmd.branchAction == commitForDiffActionDel {
 61				c.logger.Info("delete branch", logger.NewLoggerPair("branch", cmd.branch))
 62				continue
 63			}
 64
 65			if err := c.repoWriter.Checkout(cmd.branch); err != nil {
 66				c.logger.Error("can't Checkout from cmd", err, logger.NewLoggerPair("branch", cmd.branch))
 67				continue
 68			}
 69
 70			if init := c.module.ExportedFunction("init"); init != nil {
 71				arg, err := pluginRun.Marshal()
 72				if err != nil {
 73					c.logger.Error("can't Marshal pluginRun", err, logger.NewLoggerPair("branch", cmd.branch))
 74					continue
 75				}
 76				c.logger.Debug("init plugin", logger.NewLoggerPair("name", c.plugin.Name), logger.NewLoggerPair("arg", arg))
 77				if err := r.writeMemoryAndCall(c.module, init, malloc, free, c.repo.Name(), "false", string(arg)); err != nil {
 78					c.logger.Error("can't init plugin", err, logger.NewLoggerPair("branch", cmd.branch), logger.NewLoggerPair("plugin", c.plugin.Name))
 79					continue
 80				}
 81			}
 82
 83			for _, com := range cmd.commits {
 84				comMarshalled, err := MarshallOne(cmd.branch.Short(), com)
 85				if err != nil {
 86					c.logger.Error("can't marshall commit", err, logger.NewLoggerPair("branch", cmd.branch))
 87					continue
 88				}
 89				if startCommit != nil {
 90					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)))
 91					if err := r.writeMemoryAndCall(c.module, startCommit, malloc, free, comMarshalled); err != nil {
 92						c.logger.Error("startCommit error", err, logger.NewLoggerPair("branch", cmd.branch), logger.NewLoggerPair("plugin", c.plugin.Name))
 93						continue
 94					}
 95				}
 96
 97				for _, f := range com.files {
 98					if f.action == commitForDiffActionAdd && callOnAdd {
 99						c.logger.Debug("add", logger.NewLoggerPair("confPath", pluginRun.Path), logger.NewLoggerPair("currentPath", f.path))
100						if pluginRun.glob.Match(f.path) {
101							// creation
102							r.writeMemoryAndCall(c.module, addFile, malloc, free, f.path)
103						}
104					} else {
105						if pluginRun.glob.Match(f.path) {
106							if f.action == commitForDiffActionDel && callOnDel {
107								// deletion
108								r.writeMemoryAndCall(c.module, delFile, malloc, free, f.path)
109							} else if f.action == commitForDiffActionMod && callOnMod {
110								//modification
111								r.writeMemoryAndCall(c.module, modFile, malloc, free, f.oldPath, f.path)
112							}
113						}
114					}
115				}
116
117				if endCommit != nil {
118					if err := r.writeMemoryAndCall(c.module, endCommit, malloc, free, comMarshalled); err != nil {
119						c.logger.Error("endCommit error", err, logger.NewLoggerPair("branch", cmd.branch), logger.NewLoggerPair("plugin", c.plugin.Name))
120						continue
121					}
122				}
123			}
124
125			if finish := c.module.ExportedFunction("finish"); finish != nil {
126				if _, err := finish.Call(ctx); err != nil {
127					c.logger.Error("finish error", err, logger.NewLoggerPair("branch", cmd.branch), logger.NewLoggerPair("plugin", c.plugin.Name))
128					continue
129				}
130			}
131		}
132	}
133
134	if err := c.repoWriter.Checkout(currentBranch); err != nil {
135		c.logger.Error("can't Checkout currentBranch", err, logger.NewLoggerPair("branch", currentBranch.Short()))
136		return err
137	}
138	return nil
139}
140
141func (r *runtime) writeMemoryAndCall(module api.Module, toCall api.Function, malloc api.Function, free api.Function, message ...string) error {
142	params := make([]uint64, 0)
143	for _, m := range message {
144		size := uint64(len(m))
145
146		results, err := malloc.Call(r.ctx, size)
147		if err != nil {
148			return oops.Wrapf(err, "can't malloc memory for %s with %s", toCall.Definition().Name(), m)
149		}
150		ptr := results[0]
151
152		// The pointer is a linear memory offset, which is where we write the name.
153		if !module.Memory().Write(uint32(ptr), []byte(m)) {
154			return oops.Wrapf(err, "can't write memory")
155		}
156
157		params = append(params, ptr, size)
158	}
159
160	defer func() {
161		for i, d := range params {
162			if i%2 == 0 {
163				if free != nil && len(free.Definition().ParamTypes()) == 1 {
164					_, err := free.Call(r.ctx, d)
165					if err != nil {
166						r.logger.Error("can't free writeMemoryAndCall", err)
167					}
168				} else if free != nil {
169					_, err := free.Call(r.ctx, d, params[i+1])
170					if err != nil {
171						r.logger.Error("can't free(ptr, size) writeMemoryAndCall", err, logger.NewLoggerPair("nbParam", len(free.Definition().ParamTypes())))
172					}
173				}
174			}
175
176		}
177	}()
178
179	if _, err := toCall.Call(r.ctx, params...); err != nil {
180		return oops.With("method", toCall.Definition().ExportNames()).Wrapf(err, "can't call")
181	}
182
183	return nil
184}