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