// SPDX-FileCopyrightText: 2025 Romain Maneschi // // SPDX-License-Identifier: EUPL-1.2 package repository import ( "context" _ "embed" "fmt" "time" git "github.com/go-git/go-git/v6" branch "github.com/go-git/go-git/v6/config" "github.com/go-git/go-git/v6/plumbing" "github.com/go-git/go-git/v6/plumbing/client" "github.com/go-git/go-git/v6/plumbing/format/config" "github.com/go-git/go-git/v6/plumbing/transport/ssh" "github.com/samber/oops" "gitroot.dev/server/logger" "gitroot.dev/server/user" ) //go:embed resources/standard_README.md var standard_README []byte //go:embed resources/forgerepo_README.md var forgerepo_README []byte func (m *Manager) CreateRootRepoIfNeeded(ctx context.Context, rootRemote string) error { if !m.Exists(m.conf.ForgeConfigName()) { return m.createRootRepo(ctx, rootRemote) } return nil } type extraFiles struct { path string content []byte } func (m *Manager) createInitializedRepo(ctx context.Context, repoConf RepoConf, extraFiles []extraFiles, users []user.SimpleUser, availablePlugins []byte) error { finish := m.logger.Time(fmt.Sprintf("create repo %s", repoConf.Name)) defer finish() writeLock := m.repoLocks.WillWrite(repoConf.Name) unlock := writeLock.Write() if repoConf.Kind == "" || repoConf.Kind == RepoKindNormal { bareRepo, err := git.PlainInit(m.conf.GetDirPathForRepo(repoConf.Name), true, git.WithDefaultBranch(plumbing.NewBranchReferenceName(repoConf.DefaultBranch)), git.WithObjectFormat(config.SHA1)) // TODO debug sha256 if err != nil { return oops.Wrapf(err, "PlainInitWithOptions") } if err := bareRepo.CreateBranch(&branch.Branch{ Name: repoConf.DefaultBranch, Merge: plumbing.NewBranchReferenceName(repoConf.DefaultBranch), }); err != nil { return oops.Wrapf(err, "CreateBranch") } } else { _, err := git.PlainClone(m.conf.GetDirPathForRepo(repoConf.Name), &git.CloneOptions{ Bare: true, URL: repoConf.ForkUrl, ReferenceName: plumbing.HEAD, SingleBranch: true, Tags: plumbing.NoTags, NoCheckout: true, // Mirror: true, // Progress: os.Stdout, ClientOptions: []client.Option{client.WithSSHAuth(&ssh.PublicKeys{User: "gitroot", Signer: m.userManager.RootCommiter().Signer.Signer()})}, }) if err != nil { return oops.Wrapf(err, "PlainClone") } } go func() { time.Sleep(1 * time.Millisecond) //TODO not very smart unlock() writeLock.Close() }() repo, err := m.NewGitRootRepository(repoConf.Name, nil) if err != nil { return oops.Wrapf(err, "Open repo") } defer repo.Close() repoWriter, err := repo.WillWrite(plumbing.HEAD) if err != nil { return oops.Wrapf(err, "will write") } // TODO que faire pour les fichiers en mode proxy? Override? Branch? for _, extraFile := range extraFiles { if !repo.Exists(extraFile.path) { if err := repoWriter.Write(extraFile.path, extraFile.content); err != nil { return err } } } if err := repoWriter.Write(m.conf.PathFilePlugins(), availablePlugins); err != nil { return err } if err := repoWriter.Write(m.conf.PathFileRepoConfigurationName(), newRepoConfiguration(repoConf.DefaultBranch).toFileContent()); err != nil { return err } fileUserContent, err := user.CreateFileUser( repoConf.DefaultBranch, append([]user.SimpleUser{m.userManager.RootCommiter().SimpleUser}, users...)..., ) if err != nil { return oops.Wrapf(err, "CreateFileUser") } if err := repoWriter.Write(m.conf.PathFileUsers(), fileUserContent); err != nil { return err } if _, err := repoWriter.CommitAll("init", m.userManager.RootCommiter()); err != nil { return oops.Wrapf(err, "CommitAll") } // if err := repo.repo.Prune(git.PruneOptions{}); err != nil { // return oops.Wrapf(err, "Prune after PlainClone") // } // if err := repo.repo.RepackObjects(&git.RepackConfig{}); err != nil { // return oops.Wrapf(err, "Repack after PlainClone") // } return nil } func (m *Manager) createRootRepo(ctx context.Context, rootRemote string) error { repoConf := RepoConf{Name: m.conf.ForgeConfigName(), DefaultBranch: m.conf.DefaultBranchName(), Kind: RepoKindNormal} if rootRemote != "" { repoConf = RepoConf{Name: m.conf.ForgeConfigName(), DefaultBranch: m.conf.DefaultBranchName(), Kind: RepoKindFork, ForkUrl: rootRemote} } repositoriesContent, err := toFileContent([]RepoConf{repoConf}) if err != nil { return oops.Wrapf(err, "toFileContent") } defaultConf, err := m.conf.Marshall() if err != nil { return oops.Wrapf(err, "marshall conf") } extraFiles := []extraFiles{ {path: m.conf.PathFileForgeConfig(), content: defaultConf}, {path: "README.md", content: forgerepo_README}, {path: m.conf.PathFileRepositories(), content: repositoriesContent}, {path: "first_pull", content: []byte("")}, } return m.createInitializedRepo(ctx, repoConf, extraFiles, []user.SimpleUser{}, []byte("")) } func (m *Manager) CreateUserRepo(ctx context.Context, repo RepoConf, users []user.SimpleUser, availablePlugins []byte) error { m.logger.Warn("plugins version?", logger.NewLoggerPair("p", string(availablePlugins))) owners := make([]user.SimpleUser, len(repo.Owners)) for i, u := range repo.Owners { owners[i] = user.SimpleUser{ Pseudo: "", Email: "", Ssh: u, } } return m.createInitializedRepo(ctx, repo, []extraFiles{{path: "README.md", content: standard_README}}, append(users, owners...), availablePlugins) }