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 "github.com/go-git/go-git/v6/plumbing"
9 "github.com/go-git/go-git/v6/plumbing/protocol/packp"
10 "github.com/samber/oops"
11 "gitroot.dev/server/logger"
12 "gitroot.dev/server/plugin"
13 "gitroot.dev/server/repository"
14 "gitroot.dev/server/user"
15)
16
17type postPushTaskInput struct {
18 pusher user.SimpleUser
19 repoName string
20 commands []*packp.Command
21 commitsByRef map[plumbing.ReferenceName][]plumbing.Hash
22}
23
24func (m *Manager) PostPush(pusher user.SimpleUser, repoName string, commands []*packp.Command, commitsByRef map[plumbing.ReferenceName][]plumbing.Hash) {
25 m.Start(Task{
26 call: m.postPush,
27 t: postPushTaskInput{
28 pusher: pusher,
29 repoName: repoName,
30 commands: commands,
31 commitsByRef: commitsByRef,
32 },
33 })
34}
35
36func (m *Manager) postPush(input interface{}) error {
37 postPushInput := input.(postPushTaskInput)
38 errorHandler := oops.
39 With("pusher", postPushInput.pusher).
40 With("repoName", postPushInput.repoName).
41 With("commands", len(postPushInput.commands))
42
43 if len(postPushInput.commands) == 0 { //nothing todo (push just a tag or a branch)
44 return nil
45 }
46
47 repo, err := m.repoManager.Open(logger.AddCaller(m.ctx, "postPush"), postPushInput.repoName)
48 if err != nil {
49 return errorHandler.Wrapf(err, "can't open repository")
50 }
51 defer repo.Close()
52
53 for _, c := range postPushInput.commands {
54 m.logger.Info("before needToAddUser", logger.NewLoggerPair("old", c.Old.String()), logger.NewLoggerPair("new", c.New.String()))
55 }
56
57 for _, c := range postPushInput.commands {
58 if ok, currentUser, branch, err := m.needToAddUser(c, repo, postPushInput.pusher, postPushInput.commitsByRef); ok {
59 writer, err := repo.WillWrite(c.Name)
60 if err != nil {
61 return errorHandler.Wrapf(err, "can't WillWrite")
62 }
63 newCommitHash, err := writer.AddUserInfo(currentUser, branch)
64 if err != nil {
65 return errorHandler.Wrapf(err, "can't AddUserInfo")
66 }
67 writer.Accept()
68 m.logger.Debug("accept commit to add user", logger.NewLoggerPair("hash", newCommitHash.String()))
69 repo.StopWrite()
70 m.logger.Debug("stop write")
71 c.New = newCommitHash
72 postPushInput.commitsByRef[c.Name] = append(postPushInput.commitsByRef[c.Name], newCommitHash)
73 } else if err != nil {
74 return errorHandler.Wrapf(err, "NeedToAddUser error")
75 } else {
76 m.logger.Debug("user not added", logger.NewLoggerPair("currentUser", postPushInput.pusher.Email), logger.NewLoggerPair("branch", branch))
77 }
78 }
79
80 for _, c := range postPushInput.commands {
81 m.logger.Info("after needToAddUser", logger.NewLoggerPair("old", c.Old.String()), logger.NewLoggerPair("new", c.New.String()))
82 }
83
84 cmds, err := plugin.CommandForDiffFromPackpCmd(m.ctx, m.logger, repo, postPushInput.commands, postPushInput.commitsByRef, postPushInput.pusher)
85 if err != nil {
86 return errorHandler.Wrapf(err, "CommandForDiffFromPackpCmd error")
87 }
88
89 repoConfiguration, err := repo.Configuration()
90 if err != nil {
91 return errorHandler.Wrapf(err, "can't read configuration in forgerepo")
92 }
93
94 repoPluginsFileTouchedInDefaultBranch := plugin.IsFileTouched(cmds, repoConfiguration.DefaultBranch, m.conf.PathFilePlugins())
95
96 if repoPluginsFileTouchedInDefaultBranch {
97 m.scedulerManager.LoadChanges(m.ctx, postPushInput.repoName)
98 }
99
100 if m.conf.IsForgeRepo(postPushInput.repoName) {
101 forgeRepoRepositoriesFileTouchedInDefaultBranch := plugin.IsFileTouched(cmds, repoConfiguration.DefaultBranch, m.conf.PathFileRepositories())
102 m.logger.Info(
103 "postpush commands",
104 logger.NewLoggerPair("repo", postPushInput.repoName),
105 logger.NewLoggerPair("defaultBranch", repoConfiguration.DefaultBranch),
106 logger.NewLoggerPair("forgeRepoRepositoriesFileTouchedInDefaultBranch", forgeRepoRepositoriesFileTouchedInDefaultBranch),
107 logger.NewLoggerPair("forgeRepoPluginsFileTouchedInDefaultBranch", repoPluginsFileTouchedInDefaultBranch),
108 )
109
110 if repoPluginsFileTouchedInDefaultBranch {
111 filePlugin, err := repo.ContentPluginsConf()
112 if err != nil {
113 return errorHandler.Wrapf(err, "can't open file plugin in forgerepo")
114 }
115 m.logger.Debug("start sync")
116 if err := m.pluginManager.Sync(filePlugin); err != nil {
117 return errorHandler.Wrapf(err, "can't sync plugins files")
118 }
119 defer m.syncPluginsInExistingRepos()
120 }
121
122 if forgeRepoRepositoriesFileTouchedInDefaultBranch {
123 fileContent, err := repo.ContentRepositoriesConf()
124 if err != nil {
125 errorHandler.Wrapf(err, "can't open file repositories")
126 }
127 reposToCreate, reposToDelete, err := m.repoManager.ForgeRepoMakeDiffRepos(m.ctx, fileContent)
128 if err != nil {
129 return errorHandler.Wrapf(err, "can't diff repositories between forgeConfg and local")
130 }
131 m.logger.Debug("to create", logger.NewLoggerPair("repos", reposToCreate))
132 m.syncRepos(reposToCreate, reposToDelete, postPushInput.pusher)
133 }
134 }
135
136 if err := m.pluginManager.Run(m.ctx, postPushInput.repoName, cmds); err != nil {
137 return errorHandler.Wrapf(err, "plugin error")
138 }
139
140 return nil
141}
142
143func (m *Manager) needToAddUser(c *packp.Command, repo *repository.GitRootRepository, pusher user.SimpleUser, commitsByRef map[plumbing.ReferenceName][]plumbing.Hash) (bool, *user.GitRootUser, string, error) {
144 if c.Action() == packp.Delete {
145 m.logger.Debug("needToAddUser will not add user because action", logger.NewLoggerPair("user", pusher.Email), logger.NewLoggerPair("branch", c.Name.Short()), logger.NewLoggerPair("action", c.Action()))
146 return false, nil, "", nil
147 }
148 canWrite, currentUser, err := repo.CanWrite(pusher.Ssh, c.Name.Short())
149 if err != nil {
150 return false, nil, "", err
151 }
152 m.logger.Debug("needToAddUser canWrite", logger.NewLoggerPair("user", currentUser.Email), logger.NewLoggerPair("branch", c.Name.Short()), logger.NewLoggerPair("canWrite", canWrite))
153 needToBeAdded := canWrite && currentUser.NeedToBeCreated(c.Name.Short())
154 m.logger.Debug("needToAddUser needToBeAdded", logger.NewLoggerPair("user", currentUser.Email), logger.NewLoggerPair("branch", c.Name.Short()), logger.NewLoggerPair("needToBeAdded", needToBeAdded))
155 if needToBeAdded && currentUser.Email == "" { //we need email to match commit signer against it, if we don't have it take first commit
156 if len(commitsByRef[c.Name]) > 0 {
157 com, err := repo.Commit(commitsByRef[c.Name][0])
158 if err != nil {
159 return false, nil, "", err
160 }
161 currentUser.Email = com.Author.Email
162 if currentUser.Pseudo == "" {
163 currentUser.Pseudo = com.Author.Name
164 }
165 }
166 }
167 if needToBeAdded && currentUser.Pseudo == "" {
168 currentUser.Pseudo = pusher.Pseudo
169 }
170 return needToBeAdded, currentUser, c.Name.Short(), err
171}