// SPDX-FileCopyrightText: 2025 Romain Maneschi // // SPDX-License-Identifier: EUPL-1.2 package repository import ( "errors" "github.com/go-git/go-git/v6/plumbing" "github.com/go-git/go-git/v6/plumbing/object" "github.com/go-git/go-git/v6/plumbing/protocol/packp" "github.com/go-git/go-git/v6/plumbing/storer" "gitroot.dev/server/user" ) var ErrCantWrite = errors.New("user can't write") func (repo *GitRootRepositoryWrite) Merge(fromBranch string, toBranch string, commiter *user.Commiter, pusher user.SimpleUser) (*packp.Command, error) { if ok, _, err := repo.repo.CanWrite(commiter.Signer.PublicKey(), fromBranch); err != nil || !ok { if !ok { return nil, repo.errorHandler.With("commiter", commiter.Email).With("ssh", commiter.Signer.PublicKey()).Wrapf(ErrCantWrite, "commiter can't write") } return nil, repo.errorHandler.Wrapf(err, "commiter can write err") } if ok, _, err := repo.repo.CanWrite(pusher.Ssh, fromBranch); err != nil || !ok { if !ok { return nil, repo.errorHandler.With("sshUser", pusher.Ssh).Wrapf(ErrCantWrite, "sshUser can't write") } return nil, repo.errorHandler.Wrapf(err, "sshUser can write err") } fromRef, err := repo.repoWrite.Storer.Reference(plumbing.NewBranchReferenceName(fromBranch)) if err != nil { return nil, repo.errorHandler.Wrapf(err, "fromRef error") } toRef, err := repo.repoWrite.Storer.Reference(plumbing.NewBranchReferenceName(toBranch)) if err != nil { return nil, repo.errorHandler.Wrapf(err, "toRef error") } if ok, err := isFastForward(repo.repoWrite.Storer, fromRef.Hash(), toRef.Hash()); !ok { return nil, repo.errorHandler.Errorf("fastForward not possible") } else if err != nil { return nil, repo.errorHandler.Wrapf(err, "sshUser can write err") } repo.repo.logger.Debug("merge isFastForward") toRefNew, err := repo.DeleteBranchInFilesAndCommit(toBranch) if err != nil { return nil, repo.errorHandler.Wrapf(err, "can't delete branch") } unlock := repo.lock.Write() if err := repo.repoWrite.Storer.SetReference(plumbing.NewHashReference(plumbing.NewBranchReferenceName(fromBranch), toRefNew)); err != nil { return nil, repo.errorHandler.Wrapf(err, "SetReference err") } unlock() return &packp.Command{ Name: plumbing.NewBranchReferenceName(fromBranch), Old: fromRef.Hash(), New: toRefNew, }, nil } func isFastForward(s storer.EncodedObjectStorer, old, new plumbing.Hash) (bool, error) { found := false c, err := object.GetCommit(s, new) if err != nil { return false, err } iter := object.NewCommitPreorderIter(c, nil, nil) err = iter.ForEach(func(c *object.Commit) error { if c.Hash != old { return nil } found = true return storer.ErrStop }) return found, err }