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	defer unlock()
36
37	fromRef, err := repo.repoWrite.Storer.Reference(plumbing.NewBranchReferenceName(fromBranch))
38	if err != nil {
39		return repo.errorHandler.Wrapf(err, "fromRef error")
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	if err := repo.repoWrite.Storer.SetReference(plumbing.NewHashReference(plumbing.NewBranchReferenceName(fromBranch), toRef.Hash())); err != nil {
54		return repo.errorHandler.Wrapf(err, "SetReference err")
55	}
56
57	commit, err := repo.repoWrite.CommitObject(toRef.Hash())
58	if err != nil {
59		return repo.errorHandler.With("hash", toRef.Hash()).Wrapf(err, "can't get commit")
60	}
61	newCommands := make([]*packp.Command, 0)
62	commitIter := object.NewCommitPreorderIter(commit, make(map[plumbing.Hash]bool), make([]plumbing.Hash, 0))
63	commitIter.ForEach(func(commit *object.Commit) error {
64		if commit.Hash.String() == fromRef.Hash().String() {
65			return storer.ErrStop
66		}
67		newCommands = append(newCommands, &packp.Command{
68			Name: plumbing.NewBranchReferenceName(fromBranch),
69			Old:  commit.ParentHashes[0],
70			New:  commit.Hash,
71		})
72		return nil
73	})
74	repo.repo.manager.backgroundManager.PostPush(commiter.SimpleUser, repo.repo.Name(), newCommands)
75
76	return nil
77}
78
79func isFastForward(s storer.EncodedObjectStorer, old, new plumbing.Hash) (bool, error) {
80	found := false
81
82	c, err := object.GetCommit(s, new)
83	if err != nil {
84		return false, err
85	}
86
87	iter := object.NewCommitPreorderIter(c, nil, nil)
88	err = iter.ForEach(func(c *object.Commit) error {
89		if c.Hash != old {
90			return nil
91		}
92
93		found = true
94		return storer.ErrStop
95	})
96	return found, err
97}