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 repository
  6
  7import (
  8	"errors"
  9
 10	"github.com/go-git/go-git/v6/plumbing"
 11	"github.com/go-git/go-git/v6/plumbing/object"
 12	"github.com/go-git/go-git/v6/plumbing/protocol/packp"
 13	"github.com/go-git/go-git/v6/plumbing/storer"
 14	"gitroot.dev/server/user"
 15)
 16
 17var ErrCantWrite = errors.New("user can't write")
 18
 19type MergeRes struct {
 20	Cmd     *packp.Command
 21	Commits []plumbing.Hash
 22}
 23
 24func (repo *GitRootRepositoryWrite) Merge(fromBranch string, toBranch string, commiter *user.Commiter, pusher user.SimpleUser) (*MergeRes, error) {
 25	if ok, _, err := repo.repo.CanWrite(commiter.Signer.PublicKey(), fromBranch); err != nil || !ok {
 26		if !ok {
 27			return nil, repo.errorHandler.With("commiter", commiter.Email).With("ssh", commiter.Signer.PublicKey()).Wrapf(ErrCantWrite, "commiter can't write")
 28		}
 29		return nil, repo.errorHandler.Wrapf(err, "commiter can write err")
 30	}
 31
 32	if ok, _, err := repo.repo.CanWrite(pusher.Ssh, fromBranch); err != nil || !ok {
 33		if !ok {
 34			return nil, repo.errorHandler.With("sshUser", pusher.Ssh).Wrapf(ErrCantWrite, "sshUser can't write")
 35		}
 36		return nil, repo.errorHandler.Wrapf(err, "sshUser can write err")
 37	}
 38
 39	fromRef, err := repo.repoWrite.Storer.Reference(plumbing.NewBranchReferenceName(fromBranch))
 40	if err != nil {
 41		return nil, repo.errorHandler.Wrapf(err, "fromRef error")
 42	}
 43
 44	toRef, err := repo.repoWrite.Storer.Reference(plumbing.NewBranchReferenceName(toBranch))
 45	if err != nil {
 46		return nil, repo.errorHandler.Wrapf(err, "toRef error")
 47	}
 48
 49	if ok, err := isFastForward(repo.repoWrite.Storer, fromRef.Hash(), toRef.Hash()); !ok {
 50		return nil, repo.errorHandler.Errorf("fastForward not possible")
 51	} else if err != nil {
 52		return nil, repo.errorHandler.Wrapf(err, "sshUser can write err")
 53	}
 54
 55	repo.repo.logger.Debug("merge isFastForward")
 56
 57	toRefNew, err := repo.DeleteBranchInFilesAndCommit(toBranch)
 58	if err != nil {
 59		return nil, repo.errorHandler.Wrapf(err, "can't delete branch")
 60	}
 61
 62	unlock := repo.lock.Write()
 63
 64	if err := repo.repoWrite.Storer.SetReference(plumbing.NewHashReference(plumbing.NewBranchReferenceName(fromBranch), toRefNew)); err != nil {
 65		return nil, repo.errorHandler.Wrapf(err, "SetReference err")
 66	}
 67
 68	unlock()
 69
 70	commits := []plumbing.Hash{}
 71	repo.WalkCommit(fromRef.Hash(), toRefNew, func(c *object.Commit) error {
 72		commits = append(commits, c.Hash)
 73		return nil
 74	})
 75
 76	return &MergeRes{
 77		Cmd: &packp.Command{
 78			Name: plumbing.NewBranchReferenceName(fromBranch),
 79			Old:  fromRef.Hash(),
 80			New:  toRefNew,
 81		},
 82		Commits: commits,
 83	}, nil
 84}
 85
 86func isFastForward(s storer.EncodedObjectStorer, old, new plumbing.Hash) (bool, error) {
 87	found := false
 88
 89	c, err := object.GetCommit(s, new)
 90	if err != nil {
 91		return false, err
 92	}
 93
 94	iter := object.NewCommitPreorderIter(c, nil, nil)
 95	err = iter.ForEach(func(c *object.Commit) error {
 96		if c.Hash != old {
 97			return nil
 98		}
 99
100		found = true
101		return storer.ErrStop
102	})
103	return found, err
104}