GitRoot

Craft your forge, Build your project, Grow your community freely
  1// SPDX-FileCopyrightText: 2026 Romain Maneschi <romain@gitroot.dev>
  2//
  3// SPDX-License-Identifier: EUPL-1.2
  4
  5package plugin
  6
  7import (
  8	"context"
  9	"fmt"
 10	"slices"
 11
 12	"github.com/go-git/go-git/v6/plumbing"
 13	"github.com/go-git/go-git/v6/plumbing/protocol/packp"
 14	"github.com/samber/oops"
 15	"gitroot.dev/libs/golang/plugin/model"
 16	"gitroot.dev/server/logger"
 17)
 18
 19type CmdToExec struct {
 20	commands     []*packp.Command
 21	commitsByRef map[plumbing.ReferenceName][]plumbing.Hash
 22}
 23
 24func (r *runtime) schedule(ctx context.Context, repoName string, plugin Plugin, pluginRun PluginRun) error {
 25	r.command = nil
 26	r.commit = nil
 27	r.plugin = plugin
 28
 29	timerStop := r.logger.Time(fmt.Sprintf("Timer %s", plugin.Log()))
 30
 31	repo, err := r.manager.repoManager.Open(logger.AddCaller(r.ctx, "runtime.schedule"), repoName)
 32	if err != nil {
 33		return oops.Wrapf(err, "can't open repo")
 34	}
 35	r.repo = repo
 36
 37	commandsToExec := []CmdToExec{}
 38
 39	defer func() {
 40		r.repo = nil
 41		r.repoWriter = nil
 42		r.command = nil
 43		r.commitHook = nil
 44		r.commit = nil
 45		r.logger.Debug("will close repo in schedule")
 46		repo.Close()
 47		r.logger.Debug("start next cmd for schedule")
 48		for _, cmd := range commandsToExec {
 49			r.manager.backgroundManager.PostPush(plugin.commiter.SimpleUser, repoName, cmd.commands, cmd.commitsByRef)
 50		}
 51	}()
 52
 53	branches, err := repo.Branches()
 54	if err != nil {
 55		return oops.Wrapf(err, "can't get branches")
 56	}
 57
 58	m, fs, err := r.loadModule(ctx, plugin)
 59	if err != nil {
 60		return oops.Wrapf(err, "loadModule for schedule error")
 61	}
 62	l := logger.NewLogger(logger.WASM)
 63	l.Debug("memory before schedule", logger.NewLoggerPair("size", m.Memory().Size()), logger.NewLoggerPair("plugin", plugin.Log()))
 64
 65	branchesToRun := branchesToRunOn(pluginRun.Branch, branches)
 66
 67	for _, branch := range branchesToRun {
 68		r.logger.Debug("start branch plugin schedule", logger.NewLoggerPair("branch", branch), logger.NewLoggerPair("repo", repoName), logger.NewLoggerPair("name", plugin.Log()))
 69		repoWriter, err := repo.WillWrite(branch)
 70		if err != nil {
 71			r.logger.Error("can't WillWrite for schedule", err, logger.NewLoggerPair("name", plugin.Log()))
 72			continue
 73		}
 74		r.repoWriter = repoWriter
 75		fs.Git(r.repoWriter.ToFs(ctx))
 76
 77		refOld, err := repoWriter.Storer().Reference(branch)
 78		if err != nil {
 79			r.logger.Error("can't repoWriter.Storer().Reference for schedule", err, logger.NewLoggerPair("name", plugin.Log()))
 80			repo.StopWrite()
 81			continue
 82		}
 83
 84		newCommitsHash := []plumbing.Hash{}
 85		r.commitHook = func(hash plumbing.Hash) {
 86			newCommitsHash = append(newCommitsHash, hash)
 87		}
 88
 89		malloc := m.ExportedFunction("gitrootAlloc")
 90		if malloc == nil {
 91			malloc = m.ExportedFunction("malloc")
 92		}
 93
 94		isAuthorized := checkBranch(pluginRun, branch)
 95		if !isAuthorized {
 96			repo.StopWrite()
 97			continue
 98		}
 99		r.pluginRun = pluginRun
100		if init := m.ExportedFunction("init"); init != nil {
101			arg, err := pluginRun.Marshal()
102			if err != nil {
103				r.logger.Error("can't marshall pluginRun", err, logger.NewLoggerPair("name", plugin.Log()))
104				repo.StopWrite()
105				continue
106			}
107			r.logger.Debug("init plugin schedule", logger.NewLoggerPair("name", plugin.Log()), logger.NewLoggerPair("arg", arg))
108			if err := r.writeMemoryAndCall(m, init, malloc, repo.Name(), string(model.InitKindSchedule), string(arg)); err != nil {
109				r.logger.Error("can't writeMemoryAndCall init", err, logger.NewLoggerPair("name", plugin.Log()))
110				repo.StopWrite()
111				continue
112			}
113		}
114
115		//need to commit before generate next command
116		repo.StopWrite()
117
118		if len(newCommitsHash) > 0 {
119			packpCmd := []*packp.Command{{Name: branch, Old: refOld.Hash(), New: newCommitsHash[len(newCommitsHash)-1]}}
120			hashByRef := map[plumbing.ReferenceName][]plumbing.Hash{
121				branch: newCommitsHash,
122			}
123			commandsToExec = append(commandsToExec, CmdToExec{
124				commands:     packpCmd,
125				commitsByRef: hashByRef,
126			})
127		}
128	}
129
130	l.Debug("memory after schedule", logger.NewLoggerPair("size", m.Memory().Size()), logger.NewLoggerPair("plugin", plugin.Log()))
131	r.logger.Debug("finish plugin schedule", logger.NewLoggerPair("name", plugin.Log()))
132	timerStop()
133	return nil
134}
135
136func branchesToRunOn(confBranches []string, availableBranches []plumbing.ReferenceName) []plumbing.ReferenceName {
137	hasStar := slices.Contains(confBranches, "*")
138	return slices.DeleteFunc(availableBranches, func(b plumbing.ReferenceName) bool {
139		// remove all branches marked has not
140		if ok := slices.Contains(confBranches, fmt.Sprintf("!%s", b.Short())); ok {
141			return true
142		}
143		// do not remove rest branches
144		if ok := slices.Contains(confBranches, fmt.Sprintf("%s", b.Short())); ok {
145			return false
146		}
147		// keep the rest if star
148		return !hasStar
149	})
150}