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}