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 background
6
7import (
8 "errors"
9 "os"
10
11 "github.com/go-git/go-git/v6/plumbing"
12 "github.com/go-git/go-git/v6/plumbing/protocol/packp"
13 "github.com/samber/oops"
14 "gitroot.dev/server/logger"
15 "gitroot.dev/server/plugin"
16 "gitroot.dev/server/user"
17)
18
19type checkPluginsTaskInput struct {
20 repoName string
21 isRootRepo bool
22 availablePlugins []plugin.Plugin
23}
24
25func (m *Manager) syncPluginsInExistingRepos() {
26 plugins, err := m.pluginManager.Availables(m.ctx)
27 if err != nil {
28 m.logger.Error("can't get availables plugins", err)
29 return
30 }
31
32 entries, err := os.ReadDir(m.conf.PathRepositories())
33 if err != nil {
34 m.logger.Error("can't read dir of repositories", err)
35 return
36 }
37 entriesName := make([]string, len(entries))
38 for i, e := range entries {
39 entriesName[i] = e.Name()
40 }
41
42 m.logger.Debug("start sync plugins in repos", logger.NewLoggerPairNamedSlice("plugins", plugins), logger.NewLoggerPair("repos", entriesName))
43
44 for _, repoName := range entriesName {
45 m.Start(Task{
46 call: m.checkPlugins,
47 t: checkPluginsTaskInput{
48 repoName: repoName,
49 isRootRepo: m.conf.IsForgeRepo(repoName),
50 availablePlugins: plugins,
51 },
52 })
53 }
54}
55
56func (m *Manager) checkPlugins(input interface{}) error {
57 checkInput := input.(checkPluginsTaskInput)
58 repoName := checkInput.repoName
59 isRootRepo := checkInput.isRootRepo
60 availablePlugins := checkInput.availablePlugins
61
62 repo, err := m.repoManager.Open(logger.AddCaller(m.ctx, "checkPlugins"), repoName)
63 if err != nil {
64 return oops.Wrapf(err, "can't open repo")
65 }
66 defer repo.Close()
67
68 defaultBranch, err := repo.DefaultBranch()
69 if err != nil {
70 return oops.Wrapf(err, "can't get default branch")
71 }
72
73 hash, err := repo.DefaultBranchCurrentHash()
74 if errors.Is(err, plumbing.ErrReferenceNotFound) { //repo has no head == it is empty == nothing todo
75 return nil
76 }
77 if err != nil {
78 return oops.Wrapf(err, "can't status")
79 }
80
81 conf, err := repo.Configuration()
82 if err != nil {
83 return oops.Wrapf(err, "can't get conf")
84 }
85
86 repoWriter, err := repo.WillWrite(conf.DefaultBranch)
87 if err != nil {
88 return oops.Wrapf(err, "can't will write")
89 }
90
91 pluginsContent, err := repo.ContentPluginsConf()
92 if err != nil {
93 return oops.Wrapf(err, "can't get plugin conf")
94 }
95 plugins, err := plugin.ParsePlugins(pluginsContent, false)
96 if err != nil {
97 return oops.Wrapf(err, "can't parse plugin conf")
98 }
99
100 usersToAdd := make([]user.SimpleUser, 0)
101 usersToDel := make([]user.SimpleUser, 0)
102
103 for _, ap := range availablePlugins {
104 found := false
105 for i, p := range plugins {
106 if ap.Equal(p) {
107 found = true
108 plugins[i] = p.OverrideWith(ap).SetActive(p.Active) //replace plugin in project by fullloaded one
109 break
110 }
111 }
112 if !found {
113 plugins = append(plugins, ap.SetActive(false))
114 }
115 usersToAdd = append(usersToAdd, ap.Commiter().SimpleUser)
116 }
117
118 if len(availablePlugins) > 0 {
119 for i, p := range plugins {
120 found := false
121 for _, ap := range availablePlugins {
122 if ap.Equal(p) {
123 found = true
124 break
125 }
126 }
127 if !found {
128 plugins = append(plugins[:i], plugins[i+1:]...)
129 if p.Commiter() != nil {
130 usersToDel = append(usersToDel, p.Commiter().SimpleUser)
131 }
132 }
133 }
134 }
135
136 newPluginsContent, _ := m.pluginManager.ToNewRepo(isRootRepo, false, plugins)
137
138 if err := repoWriter.Write(m.conf.PathFilePlugins(), newPluginsContent); err != nil {
139 return oops.Wrapf(err, "can't write new plugin conf")
140 }
141
142 fileUsersContent, err := repo.AppendUserToGroup("owner", usersToAdd...)
143 if err != nil {
144 return oops.With("userToAdd", usersToAdd).Wrapf(err, "can't add user to users file")
145 }
146 err = repoWriter.Write(m.conf.PathFileUsers(), fileUsersContent)
147 if err != nil {
148 return oops.Wrapf(err, "can't write users file")
149 }
150
151 // TODO delete
152
153 m.logger.Debug("sync plugins", logger.NewLoggerPair("repo", repoName))
154 newHash, err := repoWriter.CommitAll("sync plugins", m.userManager.RootCommiter())
155 if err != nil {
156 return err
157 }
158
159 if newHash == plumbing.ZeroHash { //nothing change == nothing todo
160 return nil
161 }
162
163 m.PostPush(
164 m.userManager.RootCommiter().SimpleUser,
165 repoName,
166 []*packp.Command{{Name: defaultBranch, Old: hash, New: newHash}},
167 map[plumbing.ReferenceName][]plumbing.Hash{defaultBranch: {newHash}},
168 )
169
170 return nil
171}