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) (*packp.Command, error) {
20	if ok, _, err := repo.repo.CanWrite(commiter.Signer.PublicKey(), fromBranch); err != nil || !ok {
21		if !ok {
22			return nil, repo.errorHandler.With("commiter", commiter.Email).With("ssh", commiter.Signer.PublicKey()).Wrapf(ErrCantWrite, "commiter can't write")
23		}
24		return nil, 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 nil, repo.errorHandler.With("sshUser", pusher.Ssh).Wrapf(ErrCantWrite, "sshUser can't write")
30		}
31		return nil, repo.errorHandler.Wrapf(err, "sshUser can write err")
32	}
33
34	fromRef, err := repo.repoWrite.Storer.Reference(plumbing.NewBranchReferenceName(fromBranch))
35	if err != nil {
36		return nil, repo.errorHandler.Wrapf(err, "fromRef error")
37	}
38
39	toRef, err := repo.repoWrite.Storer.Reference(plumbing.NewBranchReferenceName(toBranch))
40	if err != nil {
41		return nil, repo.errorHandler.Wrapf(err, "toRef error")
42	}
43
44	if ok, err := isFastForward(repo.repoWrite.Storer, fromRef.Hash(), toRef.Hash()); !ok {
45		return nil, repo.errorHandler.Errorf("fastForward not possible")
46	} else if err != nil {
47		return nil, repo.errorHandler.Wrapf(err, "sshUser can write err")
48	}
49
50	repo.repo.logger.Debug("merge isFastForward")
51
52	toRefNew, err := repo.DeleteBranchInFilesAndCommit(toBranch)
53	if err != nil {
54		return nil, repo.errorHandler.Wrapf(err, "can't delete branch")
55	}
56
57	unlock := repo.lock.Write()
58
59	if err := repo.repoWrite.Storer.SetReference(plumbing.NewHashReference(plumbing.NewBranchReferenceName(fromBranch), toRefNew)); err != nil {
60		return nil, repo.errorHandler.Wrapf(err, "SetReference err")
61	}
62
63	unlock()
64	return &packp.Command{
65		Name: plumbing.NewBranchReferenceName(fromBranch),
66		Old:  fromRef.Hash(),
67		New:  toRefNew,
68	}, nil
69}
70
71func isFastForward(s storer.EncodedObjectStorer, old, new plumbing.Hash) (bool, error) {
72	found := false
73
74	c, err := object.GetCommit(s, new)
75	if err != nil {
76		return false, err
77	}
78
79	iter := object.NewCommitPreorderIter(c, nil, nil)
80	err = iter.ForEach(func(c *object.Commit) error {
81		if c.Hash != old {
82			return nil
83		}
84
85		found = true
86		return storer.ErrStop
87	})
88	return found, err
89}