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("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 from cmd", 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					c.logger.Error("can't Marshal pluginRun", err, logger.NewLoggerPair("branch", cmd.branch))
 68					continue
 69				}
 70				c.logger.Debug("init plugin", logger.NewLoggerPair("name", c.plugin.Name), logger.NewLoggerPair("arg", arg))
 71				if err := writeMemoryAndCall(ctx, c.module, init, malloc, free, c.repo.Name(), "false", string(arg)); err != nil {
 72					c.logger.Error("can't init plugin", err, logger.NewLoggerPair("branch", cmd.branch), logger.NewLoggerPair("plugin", c.plugin.Name))
 73					continue
 74				}
 75			}
 76
 77			for _, com := range cmd.commits {
 78				comMarshalled, err := MarshallOne(cmd.branch.Short(), com)
 79				if err != nil {
 80					c.logger.Error("can't marshall commit", err, logger.NewLoggerPair("branch", cmd.branch))
 81					continue
 82				}
 83				if startCommit != nil {
 84					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)))
 85					if err := writeMemoryAndCall(ctx, c.module, startCommit, malloc, free, comMarshalled); err != nil {
 86						c.logger.Error("startCommit error", err, logger.NewLoggerPair("branch", cmd.branch), logger.NewLoggerPair("plugin", c.plugin.Name))
 87						continue
 88					}
 89				}
 90
 91				for _, f := range com.files {
 92					if f.action == commitForDiffActionAdd && callOnAdd {
 93						c.logger.Debug("add", logger.NewLoggerPair("confPath", pluginRun.Path), logger.NewLoggerPair("currentPath", f.path))
 94						if pluginRun.glob.Match(f.path) {
 95							// creation
 96							writeMemoryAndCall(ctx, c.module, addFile, malloc, free, f.path)
 97						}
 98					} else {
 99						if pluginRun.glob.Match(f.path) {
100							if f.action == commitForDiffActionDel && callOnDel {
101								// deletion
102								writeMemoryAndCall(ctx, c.module, delFile, malloc, free, f.path)
103							} else if f.action == commitForDiffActionMod && callOnMod {
104								//modification
105								writeMemoryAndCall(ctx, c.module, modFile, malloc, free, f.oldPath, f.path)
106							}
107						}
108					}
109				}
110
111				if endCommit != nil {
112					if err := writeMemoryAndCall(ctx, c.module, endCommit, malloc, free, comMarshalled); err != nil {
113						c.logger.Error("endCommit error", err, logger.NewLoggerPair("branch", cmd.branch), logger.NewLoggerPair("plugin", c.plugin.Name))
114						continue
115					}
116				}
117			}
118
119			if finish := c.module.ExportedFunction("finish"); finish != nil {
120				if _, err := finish.Call(ctx); err != nil {
121					c.logger.Error("finish error", err, logger.NewLoggerPair("branch", cmd.branch), logger.NewLoggerPair("plugin", c.plugin.Name))
122					continue
123				}
124			}
125		}
126	}
127
128	if err := c.repoWriter.Checkout(currentBranch); err != nil {
129		c.logger.Error("can't Checkout currentBranch", err, logger.NewLoggerPair("branch", currentBranch.Short()))
130		return err
131	}
132	return nil
133}
134
135func writeMemoryAndCall(ctx context.Context, module api.Module, toCall api.Function, malloc api.Function, free api.Function, message ...string) error {
136	params := make([]uint64, 0)
137	for _, m := range message {
138		size := uint64(len(m))
139
140		results, err := malloc.Call(ctx, size)
141		if err != nil {
142			return oops.Wrapf(err, "can't malloc memory for %s with %s", toCall.Definition().Name(), m)
143		}
144		ptr := results[0]
145
146		// The pointer is a linear memory offset, which is where we write the name.
147		if !module.Memory().Write(uint32(ptr), []byte(m)) {
148			return oops.Wrapf(err, "can't write memory")
149		}
150
151		params = append(params, ptr, size)
152	}
153
154	defer func() {
155		for i, d := range params {
156			if i%2 == 0 {
157				free.Call(ctx, d)
158			}
159		}
160	}()
161
162	if _, err := toCall.Call(ctx, params...); err != nil {
163		return oops.With("method", toCall.Definition().ExportNames()).Wrapf(err, "can't call")
164	}
165
166	return nil
167}