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 := needToAddUser(c, repo, postPushInput.pusher); 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		}
 76	}
 77
 78	for _, c := range postPushInput.commands {
 79		m.logger.Info("after needToAddUser", logger.NewLoggerPair("old", c.Old.String()), logger.NewLoggerPair("new", c.New.String()))
 80	}
 81
 82	cmds, err := plugin.CommandForDiffFromPackpCmd(m.ctx, m.logger, repo, postPushInput.commands, postPushInput.commitsByRef, postPushInput.pusher)
 83	if err != nil {
 84		return errorHandler.Wrapf(err, "CommandForDiffFromPackpCmd error")
 85	}
 86
 87	repoConfiguration, err := repo.Configuration()
 88	if err != nil {
 89		return errorHandler.Wrapf(err, "can't read configuration in forgerepo")
 90	}
 91
 92	repoPluginsFileTouchedInDefaultBranch := plugin.IsFileTouched(cmds, repoConfiguration.DefaultBranch, m.conf.PathFilePlugins())
 93
 94	if repoPluginsFileTouchedInDefaultBranch {
 95		m.scedulerManager.LoadChanges(m.ctx, postPushInput.repoName)
 96	}
 97
 98	if m.conf.IsForgeRepo(postPushInput.repoName) {
 99		forgeRepoRepositoriesFileTouchedInDefaultBranch := plugin.IsFileTouched(cmds, repoConfiguration.DefaultBranch, m.conf.PathFileRepositories())
100		m.logger.Info(
101			"postpush commands",
102			logger.NewLoggerPair("repo", postPushInput.repoName),
103			logger.NewLoggerPair("defaultBranch", repoConfiguration.DefaultBranch),
104			logger.NewLoggerPair("forgeRepoRepositoriesFileTouchedInDefaultBranch", forgeRepoRepositoriesFileTouchedInDefaultBranch),
105			logger.NewLoggerPair("forgeRepoPluginsFileTouchedInDefaultBranch", repoPluginsFileTouchedInDefaultBranch),
106		)
107
108		if repoPluginsFileTouchedInDefaultBranch {
109			filePlugin, err := repo.ContentPluginsConf()
110			if err != nil {
111				return errorHandler.Wrapf(err, "can't open file plugin in forgerepo")
112			}
113			m.logger.Debug("start sync")
114			if err := m.pluginManager.Sync(filePlugin); err != nil {
115				return errorHandler.Wrapf(err, "can't sync plugins files")
116			}
117			defer m.syncPluginsInExistingRepos()
118		}
119
120		if forgeRepoRepositoriesFileTouchedInDefaultBranch {
121			fileContent, err := repo.ContentRepositoriesConf()
122			if err != nil {
123				errorHandler.Wrapf(err, "can't open file repositories")
124			}
125			reposToCreate, reposToDelete, err := m.repoManager.ForgeRepoMakeDiffRepos(m.ctx, fileContent)
126			if err != nil {
127				return errorHandler.Wrapf(err, "can't diff repositories between forgeConfg and local")
128			}
129			m.logger.Debug("to create", logger.NewLoggerPair("repos", reposToCreate))
130			m.syncRepos(reposToCreate, reposToDelete, postPushInput.pusher)
131		}
132	}
133
134	if err := m.pluginManager.Run(m.ctx, postPushInput.repoName, cmds); err != nil {
135		return errorHandler.Wrapf(err, "plugin error")
136	}
137
138	return nil
139}
140
141func needToAddUser(c *packp.Command, repo *repository.GitRootRepository, pusher user.SimpleUser) (bool, *user.GitRootUser, string, error) {
142	if c.Action() == packp.Delete || c.Action() == packp.Update {
143		return false, nil, "", nil
144	}
145	canWrite, currentUser, err := repo.CanWrite(pusher.Ssh, c.Name.Short())
146	needToBeAdded := canWrite && currentUser.NeedToBeCreated(c.Name.Short())
147	return needToBeAdded, currentUser, c.Name.Short(), err
148}