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}