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}