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